ARTICLE DETAIL

资讯详情

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

Spring Boot社区康养系统开发全流程:从数据库设计到答辩通关指南

Spring Boot社区康养系统开发全流程:从数据库设计到答辩通关指南 每年到课程设计和毕业设计的季节总能看到一批同学在“社区健康管理/康养管理”这类题目上反复挣扎。题目看着不难但真正动手时才发现它跟普通的增删改查系统不太一样——你要面对的不是一张简单的业务表而是“老人档案 健康数据 服务流转 预警通知”这一整条链路的闭环。这篇就以我实际做过的基于Spring Boot的社区康养管理系统为例把从需求拆解、技术选型、数据库设计到核心接口实现、论文写作、答辩演示的完整过程从头梳理一遍。无论你手里有没有完整的源码和万字文档看完这篇至少能搞明白这个系统到底该怎么做、哪些地方容易踩坑、哪些模块是评阅老师重点关注的对象。1. 接这个题目前先把“康养管理”四个字拆明白很多同学拿到题目第一反应是建工程、写CRUD结果写到一半发现业务说不通。社区康养管理系统不是简单的老人信息登记表它本质上是一个带有“健康数据采集—风险评估—服务派发—结果反馈”链条的管理平台。1.1 系统里到底有哪几类人先列角色这是建表的基础。我按最常见的权限模型拆成四类超级管理员负责系统配置、账号管理、数据总览能看到所有社区的数据。社区工作人员核心操作者负责录入老人档案、维护健康记录、创建服务工单、派单给护理员。护理员/服务人员接收工单、更新服务进度、填写服务结果。老人家属/老人本人查看档案、健康数据、预约服务、接收预警提醒。这四类角色决定了权限拦截的粒度。最省事的做法是用角色字段加拦截器判断不要一上来就引入Spring Security OAuth2那套毕设阶段会把大量时间耗在配置上。1.2 业务模块的边界怎么画我最终把系统切成了六个相对独立的模块系统登录与用户管理账号、角色、密码修改、个人资料。社区老人档案管理录入老人基本信息、家属联系方式、既往病史、过敏药物、居住情况。健康档案管理身高体重、血压、血糖、心率、体检报告上传、历史记录查询。服务工单管理服务项目分类、工单创建、智能派单、服务进度更新、完成回访。健康预警管理血压血糖阈值判断、异常记录生成、给家属发送预警通知。数据统计与可视化社区老人年龄结构、健康状态分布、服务完成率、月度趋势图。模块边界清楚了后面的数据库设计和接口设计才不会乱。标题里提到的“源码、数据库、万字文档”本质上都是为了把这些模块讲清楚而服务的。2. 技术栈取舍实录Spring Boot版本、持久层框架和前端框架技术选型这件事很多同学喜欢用最新的版本但做毕设和课设要的是“稳定资料多、报错好搜索”。我就踩过一次Spring Boot 3.0的坑很多旧教程不兼容最后老老实实回到2.x。2.1 为什么我把版本锁定在Spring Boot 2.7系当时选型对比下来2.7.x是最稳妥的选择。原因很直接网上绝大多数的SSM/Spring Boot教程、MyBatis Plus的官方文档、通用Mapper的示例都基于2.xJDK用1.8不用处理模块化带来的反射权限问题Spring Security的配置方式也更经典找资料容易。如果你想用Spring Boot 3.x意味着JDK至少17javax.servlet要改成jakarta.servletMyBatis Plus也得用3.5.3以上的适配版本。不是不能用但对毕设来说性价比太低没必要拿自己的时间赌生态兼容性。我最终确定的组合是技术组件版本/方案用途JDK1.8最稳定环境问题最少Spring Boot2.7.x主框架MyBatis Plus3.5.x持久层增强省大量SQLMySQL8.0.x数据存储Redis5.x/6.x (Windows版或Linux版)验证码缓存、公告缓存JWT 拦截器jjwt 0.9.x登录鉴权比Security轻Hutool5.8.x日期工具、随机数、加密Vue 2 Element UI2.x管理后台前端ECharts5.x统计图表2.2 持久层框架MyBatis Plus确实能省一半代码我的建议是不要用原生MyBatis也不要用JPA。MyBatis Plus是折中方案——既保留了SQL的可控性又给了大量的单表CRUD方法。实际开发中老人档案表、健康记录表这种单表操作直接用预先定义好的Service继承IService连SQL都不用写// 健康记录表的核心Service public interface HealthRecordService extends IServiceHealthRecord { // 根据老人ID分页查询 PageHealthRecord getHealthPage(PageHealthRecord page, Long elderId, String type); // 查询最近30天的血压记录用于趋势图 ListHealthRecord getRecentRecords(Long elderId, int days); }用它的LambdaQueryWrapper做条件查询既避免拼接SQL的注入风险代码也直观LambdaQueryWrapperHealthRecord query new LambdaQueryWrapper(); query.eq(HealthRecord::getElderId, elderId) .eq(HealthRecord::getType, 血压) .between(HealthRecord::getMeasureTime, start, end) .orderByDesc(HealthRecord::getMeasureTime);多表查询比如工单关联老人姓名、护理员姓名才手写XML这样整个项目的SQL复杂度被控制在一个很舒服的范围。Redis在这个项目里不是硬性需求但我用得很顺手登录成功后把token的“是否有效”状态存一份在Redis里实现退出登录后的token失效验证码也用Redis存设置5分钟过期防止刷新后旧验证码还能用的问题。3. 数据库建模十一张表的约定与两个容易返工的设计点数据库设计是这个项目最见功力的部分。评阅老师不一定看你页面多漂亮但一定会细看ER图和表结构。我这边最终设计了11张表下面挑重点说。3.1 用户、角色与老人档案表用户表不用多解释标准的账号密码角色模型。老人档案表是核心中的核心字段要尽可能贴近社区实际管理需求CREATE TABLE elder_info ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, user_id BIGINT DEFAULT NULL COMMENT 关联登录账号ID可为空, elder_name VARCHAR(50) NOT NULL COMMENT 老人姓名, gender TINYINT NOT NULL COMMENT 性别1男 2女, birth_date DATE NOT NULL COMMENT 出生日期, id_card VARCHAR(18) DEFAULT NULL COMMENT 身份证号, phone VARCHAR(15) DEFAULT NULL COMMENT 联系电话, address VARCHAR(255) DEFAULT NULL COMMENT 家庭住址, community_id BIGINT DEFAULT NULL COMMENT 所属社区/网格ID, emergency_contact VARCHAR(50) DEFAULT NULL COMMENT 紧急联系人, emergency_phone VARCHAR(15) DEFAULT NULL COMMENT 紧急联系电话, chronic_disease VARCHAR(255) DEFAULT NULL COMMENT 慢性病史逗号分隔, allergy_history VARCHAR(255) DEFAULT NULL COMMENT 过敏药物史, health_status TINYINT DEFAULT 1 COMMENT 健康状态1健康 2亚健康 3慢病管理 4失能, care_level TINYINT DEFAULT 1 COMMENT 照护等级1一级 2二级 3三级, remark VARCHAR(500) DEFAULT NULL COMMENT 备注, deleted TINYINT DEFAULT 0 COMMENT 逻辑删除标记, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT社区老人档案表;有一个字段细节我要专门提醒health_status和care_level不要写成字符串。状态用数字枚举前端在给用户展示时再映射成中文标签。这样在统计图表阶段会非常省事直接GROUP BY health_status就能算分布。3.2 健康记录表与服务工单表的设计思路健康记录表是预警功能的数据来源设计时多考虑“查最近N次记录”这种常见查询CREATE TABLE health_record ( id BIGINT NOT NULL AUTO_INCREMENT, elder_id BIGINT NOT NULL COMMENT 老人ID, record_type VARCHAR(20) NOT NULL COMMENT 指标类型血压/血糖/心率/体温, systolic INT DEFAULT NULL COMMENT 收缩压mmHg, diastolic INT DEFAULT NULL COMMENT 舒张压mmHg, blood_glucose DECIMAL(5,2) DEFAULT NULL COMMENT 空腹血糖, heart_rate INT DEFAULT NULL COMMENT 心率/bpm, temperature DECIMAL(4,2) DEFAULT NULL COMMENT 体温, measure_time DATETIME NOT NULL COMMENT 测量时间, operator_id BIGINT DEFAULT NULL COMMENT 录入人员ID, remark VARCHAR(255) DEFAULT NULL, deleted TINYINT DEFAULT 0, PRIMARY KEY (id), KEY idx_elder_time (elder_id, measure_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT健康记录表;这里用了“宽表”的设计不同指标共用一张表没测的字段留NULL。虽然不算严格的第三范式但胜在实际好用——前端展示历史趋势时只需要按record_type过滤不需要做复杂的表连接。缺点是录入校验时要写多几条判断逻辑这个我后面会讲到。服务工单表则是业务闭环的关键CREATE TABLE service_order ( id BIGINT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 工单编号, elder_id BIGINT NOT NULL COMMENT 老人ID, service_type_id BIGINT NOT NULL COMMENT 服务项目ID, staff_id BIGINT DEFAULT NULL COMMENT 护理员ID, creator_id BIGINT NOT NULL COMMENT 创建人ID, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态0待派单 1已派单 2执行中 3已完成 4已取消, appoint_time DATETIME DEFAULT NULL COMMENT 预约上门时间, finish_time DATETIME DEFAULT NULL COMMENT 完成时间, service_content VARCHAR(500) DEFAULT NULL COMMENT 服务内容描述, feedback VARCHAR(500) DEFAULT NULL COMMENT 完成反馈/家属评价, score TINYINT DEFAULT NULL COMMENT 服务评分1-5, deleted TINYINT DEFAULT 0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT服务工单表;3.3 两个容易被评阅老师追问的设计细节第一个是逻辑删除。所有表都要带deleted字段配合MyBatis Plus的TableLogic注解查询时自动过滤已删除数据。老师问“删除用户了怎么办”你可以答“数据保留可追溯”。第二个是时间字段统一用DATETIME而非TIMESTAMP。TIMESTAMP在2038年会溢出MySQL中还有时区转换问题。使用统一的DATETIME存活2038年的问题还能避免时区踩坑。4. 后端核心接口实现JWT登录、健康档案与工单状态机工程骨架搭好之后实现的核心接口其实就是那几条主线。我给每个模块都设计了完整的Controller Service Mapper三层结构下面把最关键的三个点展开讲。4.1 JWT登录与权限拦截是怎么落地的登录接口的逻辑链很简单接收用户名密码 → 查库校验 → 生成token → 返回前端。密码存入数据库前必须加密我用的BCryptHutool封装的BCrypt.hashpw()一行搞定。PostMapping(/login) public Result login(RequestBody LoginDTO dto) { User user userService.getOne(new LambdaQueryWrapperUser() .eq(User::getUsername, dto.getUsername())); if (user null) { return Result.error(用户不存在); } if (!BCrypt.checkpw(dto.getPassword(), user.getPassword())) { return Result.error(密码错误); } // 生成JWT有效期24小时 String token JwtUtil.createToken(user.getId(), user.getUsername(), user.getRole()); // 存入Redis用于退出登录时踢掉旧token redisTemplate.opsForValue().set(login:token: token, user.getId(), 24, TimeUnit.HOURS); return Result.success(Collections.singletonMap(token, token)); }JWT工具类我封装了三个方法创建token、解析token、校验过期。密钥写死在配置里就行毕设不需要搞动态密钥管理。权限拦截用Spring MVC的HandlerInterceptor拦截所有/api/**请求白名单放行/login和静态资源。拦截器里做三件事取Header里的token解析缓存用户信息到ThreadLocal供后续查询使用。这样每个接口都能用UserContext.getUser()拿到当前登录人省去反复在参数里传userId的麻烦。4.2 健康档案录入的“防脏数据”校验前面说了健康记录表是宽表所以录入接口写的校验逻辑比较多。核心思路就是按record_type校验对应字段非空、值域合法public void checkHealthRecord(HealthRecord record) { Assert.notNull(record.getElderId(), 老人ID不能为空); switch (record.getRecordType()) { case 血压: Assert.notNull(record.getSystolic(), 收缩压不能为空); Assert.notNull(record.getDiastolic(), 舒张压不能为空); if (record.getSystolic() 300 || record.getSystolic() 30) { throw new BizException(收缩压数据异常); } break; case 血糖: Assert.notNull(record.getBloodGlucose(), 血糖值不能为空); break; case 心率: Assert.notNull(record.getHeartRate(), 心率不能为空); break; default: throw new BizException(不支持的健康指标类型); } }这样的校验不只是给老师看实际演示时如果你往系统里录入一个“收缩压999”的数据系统能挡住这个交互本身就非常加分。4.3 服务工单的状态流转最政治正确的“状态机”工单的状态字段是0待派单 → 1已派单 → 2执行中 → 3已完成/4已取消。这个流转我专门写了一个状态更新方法每个操作都校验前置状态严格拒绝乱跳。Transactional public void changeOrderStatus(Long orderId, Integer targetStatus, Long operatorId, String remark) { ServiceOrder order orderMapper.selectById(orderId); if (order null) { throw new BizException(工单不存在); } // 校验状态流转 if (order.getStatus() 0 targetStatus 1) { // 待派单 - 已派单需要先设置护理员 } else if (order.getStatus() 1 targetStatus 2) { // 已派单 - 执行中护理员确认开始 } else if (order.getStatus() 2 targetStatus 3) { // 执行中 - 已完成填写反馈和评分 } else { throw new BizException(非法状态流转); } // 更新字段 order.setStatus(targetStatus); if (targetStatus 1) { order.setStaffId(operatorId); } else if (targetStatus 3) { order.setFinishTime(LocalDateTime.now()); } orderMapper.updateById(order); }这里加Transactional是必须的因为涉及状态判断和更新两步避免并发情况下两个请求同时把状态改坏。答辩时老师大概率会问“多个护理员同时接单怎么办”你可以顺带提一步数据库乐观锁的优化但实际答辩时能答到“状态校验事务保证”这一层已经够用。5. 让系统有“健康管理”的样子预警推送与统计图表题目叫“康养管理系统”如果只做了档案增删改查那和“社区人口管理系统”没区别。真正的加分项是两个能力健康预警和数据分析。5.1 阈值预警怎么触发才算合理我的设计是在录入健康记录后同步进行一次阈值判断。阈值常量放在单独的配置类里方便以后调整——毕设阶段可以写死在代码里但单独类目比硬编码规范得多public class HealthThreshold { // 血压阈值单位mmHg public static final int SYSTOLIC_HIGH 140; public static final int SYSTOLIC_LOW 90; public static final int DIASTOLIC_HIGH 90; public static final int DIASTOLIC_LOW 60; // 血糖阈值单位mmol/L public static final BigDecimal GLUCOSE_HIGH new BigDecimal(7.0); public static final BigDecimal GLUCOSE_LOW new BigDecimal(3.9); }判断逻辑写成一个服务方法发现某项指标超标生成一条预警记录记录老人ID、指标类型、超标值、正常参考范围、预警级别然后根据老人紧急联系电话给家属发送提醒我实现的是站内通知短信接口留着拓展位置。这样设计的好处是预警不会阻塞业务阈值判断只需要在Service层调用一次。实际演示时我通常会先录一个血压偏高的记录会引起“预警”出现在首页的预警列表中然后再录一个正常值预警列表不再新增整个过程非常直观。5.2 用ECharts做统计总览代码量不大但视觉效果拉满统计页我放了四个图表社区老人年龄分布饼图、男女比例环形图、健康状态分布柱状图、近半年服务工单完成趋势折线图。所有数据都走后端统计接口用SQL聚合后返回给前端渲染。年龄分布统计的SQL大概长这样select idcountByAgeGroup resultTypejava.util.Map SELECT CASE WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) lt; 60 THEN 60岁以下 WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) BETWEEN 60 AND 69 THEN 60-69岁 WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) BETWEEN 70 AND 79 THEN 70-79岁 WHEN TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) BETWEEN 80 AND 89 THEN 80-89岁 ELSE 90岁以上 END AS ageGroup, COUNT(*) AS cnt FROM elder_info WHERE deleted 0 GROUP BY ageGroup /select前端ECharts只需要准备两组数据ageGroup做X轴cnt做Y轴配置一个饼图/柱状图模板即可。这里我要强调一个很多同学忽略的点后端返回的数据结构要为前端量身定制。不要返回一堆字段让前端自己算直接返回前端要的[{name, value}]结构前后端对接能少写很多拼接代码。6. 前后端联调最容易翻车的三个地方跨域、时间格式化、路由权限后端功能做完前端一走查就发现各种小毛病。这三个问题是我每次做毕设项目都会碰到的提前解决好能节省一周时间。6.1 跨域问题前端Vue跑在8080端口后端Spring Boot跑在9090端口直接请求必然跨域。最稳妥的方式是后端全局允许跨域并允许携带token凭证Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }开发阶段这样最省事。等到部署到服务器前端和后端都走同一个域名用Nginx按路径转发跨域问题也就自然消失了。6.2 时间字段显示成时间戳的坑默认情况下Java 8的时间类型通过JSON序列化会变成一串数字前端拿到后不处理就直接显示特别容易踩坑。两个方案我建议都做后端全局配置LocalDateTime的序列化格式同时前端axios封装时统一格式化。Bean public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { return builder - { builder.simpleDateFormat(yyyy-MM-dd HH:mm:ss); builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); }; }时间显示是最容易评阅老师看一眼就觉得“系统不专业”的地方所以这个细节一定要处理干净。日期选择器的值、列表里的创建时间全部统一成yyyy-MM-dd HH:mm:ss。6.3 前端路由权限管理后台的路由需要和后端角色逻辑对应。我采用的方案是路由守卫里读取本地存储的role字段根据角色生成可访问的路由表。比如普通护理员登录后看不到“系统管理”菜单档案列表也只能看到与自己相关的老人。实现不复杂但能体现出你对权限设计的思考深度。// 路由守卫核心逻辑 router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (!token to.path ! /login) { next(/login); return; } const role localStorage.getItem(role); if (to.meta.roles !to.meta.roles.includes(role)) { next(/403); return; } next(); });7. 毕业设计通关策略论文结构、答辩现场、演示脚本代码写得再好论文和答辩拉胯一样是灾难。很多同学把论文当成代码说明书来写其实毕设论文考察的是“你为什么这么设计”的逻辑链。7.1 万字文档的骨架怎么安排我建议的论文结构是这样的逻辑链条绪论国家老龄化背景、社区康养概念、选题意义。不要写太大控制在2-3页。相关技术介绍Spring Boot、MyBatis Plus、JWT、Vue、ECharts各写一页每个技术讲“你用了它的什么特性”比罗列特性强十倍。需求分析画出角色用例图把四类角色和核心操作列出来。系统设计架构图、模块划分、功能结构图这部分和代码一致。数据库设计重点是ER图和各表结构说明每张表写清楚设计原因。系统实现每个模块选2-3个核心功能贴关键代码配截图讲实现思路。系统测试功能测试用例表格至少20条用例。论文写作有一个核心心法每个图表都要配一段“为什么这样设计”的说明这是提升论文档次最有效的方式。7.2 答辩现场的高频问题清单我整理了一下老师围绕这个题目最常问的几个方向为什么选Spring Boot答简化配置、内置Tomcat、生态成熟、资料多。登录是怎么实现的答JWT 拦截器 Redis控制token状态密码BCrypt加密。老人健康档案的数据怎么保证准确答前端表单校验、后端字段级别校验、阈值合理性检查三层。如果并发很高你的系统哪里需要优化答Redis缓存热点数据、接口幂等性、数据库索引、服务模块分离。预警是怎么触发的答录入健康记录后同步进行阈值判断生成预警记录并通知家属。这些问题的共同特点是只要你真的动手写了就一定能答得出来。最怕的就是代码是抄的连自己项目里哪个类干了什么事都说不清。7.3 演示脚本比你想的更重要正式答辩演示不要现场乱点我通常会按一条业务故事线来走管理员登录进入总览先用统计图表展示系统能力。新增一位老人档案填入基础信息、慢病史、紧急联系人。给这位老人添加一条血压偏高的健康记录切到预警列表展示预警生成。创建一条“上门护理”服务工单派单给护理员账号。切换账号登录护理员更新工单状态到执行中、已完成填写服务反馈。切回管理员查看工单列表的闭环状态和统计报表的变化。按照这条线演示整个系统显得逻辑完整、关联性强远比零散地每个菜单点一遍更有说服力。8. 部署上线时的环境细节与几处让我想摔键盘的坑最后一部分聊聊部署。虽然毕设答辩多数时候是本地演示但总有老师要求“你把系统跑起来我看看”提前部署到服务器能省不少心。后端打包成jar包前端打包成dist目录用Nginx做反向代理这套组合最常规# 后端打包在项目根目录执行 mvn clean package -DskipTests # 启动jar包 nohup java -jar community-health-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod app.log 21 # 前端打包 npm run build # Nginx配置示例 server { listen 80; server_name your-domain.com; location / { root /opt/community-health/dist; index index.html; try_files $uri $uri/ /index.html; # 解决前端路由刷新404 } location /api/ { proxy_pass http://127.0.0.1:9090; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }部署时最容易踩的坑有这几个数据库连接串里的时区参数serverTimezoneAsia/Shanghai没加导致时间显示不对服务器防火墙只开了80端口没开后端端口其实有Nginx转发就不用开后端端口前端路由使用history模式后直接访问子路径返回404上面配置里的try_files就是干这个的。还有个小细节生产环境用application-prod.yml保留生产配置和开发环境的数据库、日志级别区分开。我见过不少同学开发测试和生产混用一份配置结果演示时把测试数据展示给老师看场面很尴尬。另外提醒一句如果服务器内存只有1G后端JVM建议限制一下内存-Xms256m -Xmx512m加在启动命令里不然数据库后端前端三件套很容易内存不足直接OOM崩溃。最后说句实在话做了这么多课设和毕设项目我的体会是社区康养管理系统这个课题之所以年年有人选是因为它既有业务深度又有实现广度难度卡得恰到好处。但真正的分水岭不在于用没用最新框架而在于有没有把“康养”的业务逻辑做完整——档案、健康数据、预警、工单流转、统计报表这五条线只要串起来系统就已经甩开绝大多数只会增删改查的作业了。希望这篇拆解能帮你少走几个弯路。如果你正在做类似的选题别急着上手写代码先花一个晚上把所有角色的操作流程画出来再动手你会发现自己省下的时间远超想象。
返回列表