ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于SpringBoot的老年人用药服务系统设计与实现

2026/9/19 7:40:57 拓冰建站 浏览量
基于SpringBoot的老年人用药服务系统设计与实现 简介一份基于Spring Boot的老年人用药服务平台毕业论文文件格式为docx压缩包内含1个文档总大小约8.83MB。内容围绕课题研究背景、系统分析与设计实现展开重点涵盖服药信息管理、医生分配服药、用药提醒、身体检测等核心功能模块并结合数据挖掘技术对老年人用药行为进行深入分析。文档包含中英文摘要、目录、绪论及正文主体结构完整便于直接查阅和复用。适合计算机相关专业学生、毕业设计者以及初涉Spring Boot开发的读者参考可作为课题开题、系统设计或论文撰写的参考资料。目前已有83人学习浏览。通过此文档可快速了解基于Java/Spring Boot的管理系统开发思路、数据库与功能模块划分方式以及毕业论文的规范写作框架对完整理解老年人用药服务平台的实现路径和论文表达均有实用价值。1. 从社区药房到在线用药平台SpringBoot搭建老年人用药服务系统的切入点社区药房里最常见的场景是一本写满老人姓名和药品名的纸质登记簿。医生开药后手写一张纸条老人带回家家属不知道今天吃了什么医生也看不到上次的服药反馈。这个系统的价值不在于把登记表搬上网页而在于把「分配-提醒-反馈」这组动作串成一条可查询的链路医生在后台分配用药老人登录前台查看服药信息和身体检测记录监护人即使不在身边也能实时看到进度。技术选型上采用SpringBoot框架作为后端主体Java语言配合MySQL数据库浏览器端基于B/S架构直接访问部署时用Tomcat作为Web容器。整套方案不需要独立客户端一台普通配置的服务器就能跑起来这也让它成为典型的SpringBoot课程设计与毕业设计选题。2. 角色权限先行老年人用药服务平台的用例图与数据库模型设计2.1 从用例图推导出的四类角色权限边界系统用例图定义了管理员、医生、监护人、老人四个参与者这四类角色决定了后台管理模块的功能切分。管理员负责菜单管理、老人管理、医生管理、监护人管理、轮播图管理、操作日志管理属于顶层配置者。医生拥有老人管理、分配服药、服药提醒管理、药品类型管理、身体检测管理等权限核心操作集中在开药和跟踪两个维度。监护人只有首页、分配服药、服药信息三个入口本质是只读监督者。老人在前台的个人中心、药品类型、服药信息三个模块中完成自己的日常操作。这个权限模型的设计思路是越靠近数据生产端的角色权限越大越靠近数据消费端的角色越受限。角色核心功能数据操作权限页面入口管理员菜单/日志/全部管理增删改查全部数据后台首页医生分配服药/提醒/药品类型读写老人与服药数据后台医生模块监护人查看服药信息只读无写入后台监护人模块老人查看服药与个人中心只读可修改个人信息前台老人端权限设计直接影响数据库表的外键关系。老人表与监护人表之间的关联、服药信息表与医生分配表的关联都建立在谁有权限操作这条记录的基础上。开发时最容易犯的错误是让所有角色共用一个Controller导致接口权限失控。这个系统的做法是后台模块按角色拆分独立的Controller包路径再用拦截器做统一身份校验。2.2 从E-R图到MySQL表结构核心数据表设计系统涉及的核心实体包括老人、监护人、服药信息、服药提醒、医生分配记录。E-R图中老人与监护人是一对多关系一个监护人可关联多个老人医生与服药信息是一对多关系一次分配产生一条记录定时任务再根据分配记录生成提醒条目。MySQL建表时需要注意时间字段的默认值设置建议用DATETIME类型存储提醒时间用TIMESTAMP记录创建时间避免JVM时区与数据库时区不一致导致的偏移问题。CREATE TABLE medication_info ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主键, doctor_id BIGINT NOT NULL COMMENT 分配医生ID, elder_id BIGINT NOT NULL COMMENT 老人ID, drug_name VARCHAR(128) NOT NULL COMMENT 药品名称, dosage VARCHAR(64) NOT NULL COMMENT 每次用量, frequency INT DEFAULT 3 COMMENT 每日服药次数, reminder_time VARCHAR(32) NOT NULL COMMENT 提醒时间逗号分隔如08:00,12:00,18:00, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, KEY idx_elder_id (elder_id), KEY idx_doctor_id (doctor_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT服药信息表;medication_info表的reminder_time字段用逗号分隔存储多个提醒时间点比单独建一张子表更轻量适合每日服药频次固定的场景。frequency字段记录每日次数与reminder_time的时间点数量保持一致这个冗余设计便于在列表页直接展示服药频次而不需要二次解析。索引上对elder_id和doctor_id分别建普通索引因为系统最常见的查询是按老人查服药记录、按医生查分配记录这两个方向都需要覆盖。监护人表与老人表之间通过guardian_id外键关联建表时建议在老人表上加guardian_id字段而非创建关联表。原因是业务模型里一个老人最多对应一个主监护人关联表只会在多对多场景下才需要。身体检测表单独存放血压、心率、血糖等字段用elder_id关联老人每次检测生成一条新记录不做更新只做插入保留历史轨迹供医生分析。2.3 SpringBoot中的登录校验与拦截器实现登录流程中前端页面将用户名、密码、角色类型提交至后端接口后端通过UserService查询数据库验证身份。密码存储不建议使用明文可通过Spring Security的BCryptPasswordEncoder加密后入库。接口层用拦截器统一校验Session或Token校验失败直接返回401状态码。下述代码展示了基于HandlerInterceptor的登录校验实现Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); Object userInfo session.getAttribute(userInfo); if (userInfo null) { response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\msg\:\未登录或会话已过期\}); return false; } return true; } }注册拦截器时需要指定拦截路径比如后台所有接口都以/admin/**开头前台老人端以/api/elder/**开头。WebMvcConfigurer中通过addInterceptors方法注册addPathPatterns设置拦截范围excludePathPatterns放行登录接口和静态资源。需要在拦截器中区分角色权限的可以把角色编码存入userInfo对象在preHandle里二次校验request.getRequestURI()的前缀是否匹配角色范围。这种做法在非分布式场景下够用且实现成本最低相比引入Spring Security或Sa-Token框架适合这个规模的系统保持简洁。3. 核心链路拆解分配服药、提醒生成与前台数据交互3.1 分配服药接口的Controller与Service实现医生分配服药是这个系统的核心业务动作。前端表单提交老人ID、药品名称、用量、频次和提醒时间点后端Controller接收参数后调用Service完成数据落库。这里需要校验两个约束老人是否存在、医生是否被授权管理该老人。Service层通过ElderMapper查询老人记录再检查guardian_id归属防止医生越权操作。考虑到同一个药品可能被多次分配建议增加唯一索引约束防止重复提交或者在上游做幂等处理。PostMapping(/admin/doctor/assignMedication) ResponseBody public Result assignMedication(RequestBody MedicationAssignRequest request) { // 校验参数老人ID非空、药品名称非空、提醒时间格式正确 if (StringUtils.isBlank(request.getDrugName()) || StringUtils.isBlank(request.getReminderTime())) { return Result.error(药品名称和提醒时间不能为空); } Elder elder elderMapper.selectById(request.getElderId()); if (elder null) { return Result.error(老人信息不存在); } // 校验医生权限doctorId从Session中获取而非前端传参防止越权 Long doctorId (Long) session.getAttribute(currentDoctorId); MedicationInfo info new MedicationInfo(); info.setDoctorId(doctorId); info.setElderId(request.getElderId()); info.setDrugName(request.getDrugName()); info.setDosage(request.getDosage()); info.setFrequency(request.getReminderTime().split(,).length); info.setReminderTime(request.getReminderTime()); medicationInfoMapper.insert(info); return Result.success(分配成功); }这段代码里doctorId从Session获取而不是信任前端传来的参数是防止水平越权的关键一步。setFrequency通过解析reminderTime的逗号分隔来计算每日服药次数避免医生填写了口径不一致的数据。实际开发时还需要补充事务注解Transactional考虑到medication_info插入操作是单表单条记录SpringBoot默认的隐式事务足够处理只有在同时写多张表的场景下才需要显式标注。返回值用统一封装的Result对象包含code、msg、data三个字段前端axios拦截器统一判断code是否为200非200时弹出msg提示保证接口错误信息能够透传到页面。3.2 基于Spring Task的定时提醒任务实现服药提醒功能需要在设定时间向老人端推送提醒消息。系统使用Spring Task的Scheduled注解实现定时任务相比于Quartz框架重量级、需要额外配置JobDetail和TriggerSpring Task的零配置特性在这个无需分布式调度的场景下更合适。具体做法是写一个定时任务组件每30秒扫描一次medication_info表将reminder_time字段拆分后与当前时间比对匹配的记录插入到medication_remind表并标记为待确认。Component public class ReminderTask { Autowired private MedicationInfoMapper medicationInfoMapper; Autowired private MedicationRemindMapper medicationRemindMapper; Scheduled(cron 0 */1 * * * ?) public void scanReminder() { // 查询全部有效服药记录 ListMedicationInfo list medicationInfoMapper.selectList(null); String currentTime LocalTime.now().format(DateTimeFormatter.ofPattern(HH:mm)); for (MedicationInfo info : list) { // 解析逗号分隔的提醒时间点 String[] times info.getReminderTime().split(,); for (String time : times) { if (currentTime.equals(time.trim())) { MedicationRemind remind new MedicationRemind(); remind.setElderId(info.getElderId()); remind.setMedicationId(info.getId()); remind.setRemindTime(LocalDateTime.now()); remind.setStatus(0); // 0未确认1已确认 medicationRemindMapper.insert(remind); } } } } }定时任务用cron表达式0 */1 * * * ?表示每分钟的第0秒执行一次扫描。分钟级扫描会产生一定空转但考虑到服务平台老人数量级通常不超过千条全表扫描代价可接受。生产环境建议将扫描周期改成每5分钟并在reminder_time字段上加索引或者引入Redis延迟队列存储待触发的提醒事件减少数据库频繁查询的压力。生成提醒记录后需要处理老人已服药确认的逻辑老人端页面上点击确认服药按钮后端更新medication_remind表的status字段为1监护人端展示的就是这个状态位。同时需要在application.properties中配置spring.task.scheduling.enabledtrue确保定时任务随应用启动而注册。3.3 前台老人端与后台管理端的数据交互约定前端采用Vue框架时与SpringBoot后端通过RESTful接口交互。老人登录后进入首页需要加载未确认的提醒列表、最新身体检测记录和个人信息。建议将这几个静态查询合并到接口里减少页面加载时的请求次数。接口返回格式统一为{code, msg, data}结构data内部使用驼峰命名数据库字段使用下划线命名MyBatis开启map-underscore-to-camel-casetrue自动映射。页面展示数据时需要注意时间字段格式化Java端返回LocalDateTime默认序列化为ISO格式前端可配置jackson反序列化时使用yyyy-MM-dd HH:mm:ss模式。{ code: 200, msg: 操作成功, data: { elderName: 张秀英, todayMedication: [ { drugName: 阿司匹林, dosage: 100mg, time: 08:00, status: 1 }, { drugName: 阿司匹林, dosage: 100mg, time: 12:00, status: 0 } ] } }后台管理端的医生模块页面需要以表格形式展示已分配的药单列表支持按老人姓名模糊查询和按时段筛选。SpringBoot接收前端查询参数时用PageResult封装分页信息接口定义pageNum和pageSize两个默认值。医生调整已分配药品时不要直接更新medication_info的记录建议新增一条medication_adjust记录保存调整前后对比保留操作轨迹供管理员审计。老人的前台页面交互尽量保证大字体和明确的操作反馈提交表单后前端显示成功状态并跳转到对应列表页接口失败时不刷新页面而是局部展示错误信息。4. 多端数据一致与查询优化监护人端和医生端的数据库实践4.1 监护人查看老人服药记录的联表查询监护人登录后台后看到的分配服药管理实际上是跨表的聚合数据需要从medication_info表取药单从elder表取老人姓名从guardian表验证当前登录者与老人的关联关系。使用MySQL的JOIN查询能够一次完成数据组装。查询前先通过监护人ID查elder表获取关联的老人ID列表再以这个ID列表作为过滤条件查询medication_info最后按时间倒序排列展示。这里推荐先把关联关系查出来放内存里再使用WHERE elder_id IN (...)比直接在三表间JOIN更容易定位权限问题。SELECT m.id, e.elder_name, m.drug_name, m.dosage, m.frequency, m.reminder_time, m.create_time FROM medication_info m INNER JOIN elder e ON m.elder_id e.id WHERE m.elder_id IN (SELECT elder_id FROM elder WHERE guardian_id #{guardianId}) ORDER BY m.create_time DESC LIMIT #{offset}, #{pageSize};IN子查询在数据量不大时不会造成明显的性能损耗配合elder_id上的索引可快速返回结果集。如果后续老人数量增长到万级建议改成两步查询先按guardian_id从elder表查出关联的老人ID集合再传入medication_info查询这样可以让两条SQL各自走索引避免子查询的临时表开销。分页使用LIMIT加OFFSET实现传入的offset由前端页码决定后端需要限制pageSize的最大值比如不超过100防止一次性拉取过量数据占满内存。4.2 慢查询排查与索引设计医生端按照药品名称筛选记录时容易触发全表扫描尤其是drug_name字段使用LIKE %xxx%模糊匹配时普通索引失效。在开发与测试阶段就要开启MySQL慢查询日志排查哪些SQL执行时间超出预期。默认long_query_time10秒对小型系统过于宽松建议设置为1秒定期从mysqld-slow.log里提取执行慢的语句进行优化分析。SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1; SHOW VARIABLES LIKE slow_query_log%; EXPLAIN SELECT id, elder_id, drug_name FROM medication_info WHERE elder_id 10086 AND frequency 3;EXPLAIN输出的type字段为ref时说明走的是非唯一索引rows字段显示预估扫描行数这个值越小越好。当前场景下的索引策略是elder_id与create_time建复合索引idx_elder_time(elder_id, create_time)因为列表页最常见的排序方式是按老人和时间过滤reminder_time字段单独建索引供定时任务比对时间时使用drug_name字段不需要建索引因为模糊搜索场景下普通索引优化效果有限数据量大时考虑引入Elasticsearch但在这个量级先保持简单。另外注意DEFAULT CHARSET要使用utf8mb4而不是utf8否则中文和特殊符号存储时会报字符集相关错误。4.3 SpringBoot多数据源与连接池配置的边界场景当系统后续需要接入体检设备数据或对接第三方药品库时可能涉及多个数据库实例的数据读写。SpringBoot默认配置单数据源可通过MapperScan指定不同包路径扫描不同数据源配合DS注解实现动态切换。但多数据源会带来事务管理复杂度不适合在小系统中过度设计。需要在不同库间同步数据时更稳妥的方式是定时任务单方向同步保持主从关系而不是让应用同时写多个库。spring: datasource: druid: url: jdbc:mysql://localhost:3306/elder_medication?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root123 initial-size: 5 max-active: 20 min-idle: 5 max-wait: 60000 validation-query: SELECT 1这段配置中最容易忽略的参数是serverTimezoneMySQL驱动8.0以上版本指定Asia/Shanghai避免与JVM时区偏移导致时间错乱。max-wait设置为60000毫秒当连接池耗尽后请求最多等待60秒超过则抛出异常避免线程无限阻塞。validation-query会在获取连接时执行一次空查询校验连接可用性配合test-while-idle参数能在归还连接时检测断连。单数据源场景下Druid连接池的监控页面可通过/druid/index.html访问生产环境需要设置login-username和login-password保护测试环境可直接关闭监控功能。5. 提交前最后一遍自查系统测试用例与答辩验证清单5.1 功能测试的重点用例设计系统测试采用黑盒测试方法围绕登录、权限、核心业务流程展开。操作日志管理和菜单管理这类纯CRUD功能可以覆盖基础用例重点放在分配服药和服药提醒这类跨表事务上。测试过程中用到的数据要覆盖正常、边界、异常三类比如服药提醒时间点设置为23:59验证当天是否触发设置为00:00验证跨天边界设置空字符串验证接口是否拦截。测试模块输入条件预期结果测试结果登录管理员账号正确密码跳转后台首页通过登录老人账号错误密码提示用户名或密码错误通过分配服药未关联老人的医生ID提示您无权操作该老人通过服药提醒提醒时间设为当前分钟生成未确认提醒记录通过状态确认老人点击确认服药状态改为已确认通过分页查询页码超过总页数返回空列表不报错通过测试时需要注意浏览器兼容性系统开发时常用Chrome调试部署后要在Edge和国产浏览器上各跑一遍主要流程。登录接口连续提交五次错误密码后应锁定账号一段时间防止暴力破解这个逻辑在拦截器或Service层均可实现。5.2 答辩演示走哪条路径最稳妥答辩时演示路径建议按登录→医生分配→提醒生成→老人确认→监护人查看这条主线进行提前准备好测试账号和测试数据。演示中如果现场网络环境不稳定接口请求超时是最常见的问题需要在前端axios拦截器里配置超时时间为10秒并给出友好提示。分配服药操作时现场录入一个当前时间点的提醒演示完刷新页面能看到新生成的提醒记录这个交互效果比静态列表更直观。SpringBoot项目启动时若遇到Port 8080 was already in use使用netstat -ano | findstr 8080查找占用进程并结束或在启动参数中指定--server.port8081绕过端口冲突。项目部署到云服务器前将application.yml中数据库密码改为环境变量读取避免源码泄露导致数据库凭证暴露。本文还有配套的精品资源点击获取