ARTICLE DETAIL

资讯详情

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

SpringBoot养老服务平台:业务模型、表结构与权限控制实战

SpringBoot养老服务平台:业务模型、表结构与权限控制实战 简介基于SpringBoot的社区养老医疗服务综合平台管理系统源码包主要面向计算机专业毕业生、课程设计学生及需要Java项目实战的开发者解决毕设选题缺乏完整可运行工程的问题覆盖健康医疗领域的社区养老业务模块。压缩包共428个文件约2.16MB以178个Java源码文件为核心配合84个JS脚本、58个HTML页面、19个CSS样式等前端资源以及SQL数据库脚本、YML配置和项目说明文档代码分层清晰可直接导入IDE运行调试。目前已有424人学习下载适合用来做毕业设计交付或快速理解SpringBoot前后端交互。除完整源码外还附带数据库建表脚本、项目说明与目录结构梳理可帮助读者省去环境搭建与业务设计的时间借鉴后台管理、健康档案、服务预约等模块的实现思路直接作为毕设题目或课设大作业的基础骨架。1. 一个SpringBoot养老服务平台值钱的不是代码量是业务主链刚接手这个标题时我第一反应是又一个把增删改查套壳成“平台”的毕业设计。但把基于SpringBoot实现的社区养老医疗服务综合平台拆开看之后真正值得研究的不是代码行数而是它对“服务预约—上门服务—健康档案—家属可见”这条主链的拆分方式。这个标题自带源码、数据库和项目说明三件套正好对应毕业设计答辩现场最常问的三件事系统能不能跑、表结构怎么设计的、代码你敢不敢讲清楚。适合谁第一类是正在准备Java课程设计或毕设开题的学生想在两到三周内做出一个可验收、能演示的完整系统第二类是社区医疗信息化方向的中级开发想拿一套可复用的业务模型快速改造成内部原型。它解决的最大痛点是养老服务不是电商把订单模型硬套进去从状态机到字段设计都会翻车。2. 业务模型与角色权限社区养老平台为什么必须先画清四类角色社区养老医疗服务综合平台的核心不在于把“医疗”两个字做得像医院HIS那么深而在于把社区里的养老服务履约链路管理起来。这条链是老人或家属发起服务预约站点管理员审核排班服务人员上门服务并回填结果护士或医生补充健康记录最后家属能查到老人这段时间的健康档案。如果一上来就写代码很容易把系统做成单用户台账答辩时老师问“权限怎么设计的、不同角色看到什么”就只能含糊带过。所以我一般会先花半天时间把角色边界定死再反推表结构和接口。2.1 五张角色画像老人、家属、管理员、服务人员、医护在这类平台里最常见的最小角色集是下面五个缺一个都会在某个环节卡住老人是被服务对象但大多数老年人不直接操作系统操作由家属代劳。家属负责建档、预约、查看档案是整个系统的下单入口。站点管理员负责审核预约、排班、维护服务项目和站点信息。服务人员指助老员、护士、康复师他们接单、上门、回填服务结果是履约环节的执行者。医护角色可以做健康评估、体检记录、康复建议属于让系统更有“医疗味”的加分项。这五个角色落到数据库里常见做法是拆成sys_user、sys_role、sys_user_role、sys_menu、sys_role_menu五张表走一套标准RBAC。有些同学图省事直接在sys_user表里加一个role字段答辩时很容易被追问“你怎么控制接口权限”“如果一个人既是管理员又是服务人员怎么办”我的建议是哪怕不做按钮级权限控制这五张表也要建出来。因为角色分离不只是权限问题它直接决定首页菜单、接口路径和数据显示范围。接口路径可以按角色前缀区分管理员走/api/admin/**服务人员走/api/staff/**家属和老人走/api/elder/**这样在拦截器里就能做粗粒度隔离。2.2 服务工单不是电商订单三个无法照搬的状态设计很多网上流传的“管理系统”源码是从商城订单系统改过来的社区养老场景下有三个地方不能照搬。第一是状态机。电商订单是“待支付—已支付—已发货—已完成”养老服务单不能这么写。养老工单至少要有待审核、待服务、服务中、已完成、已取消、已回访。前面多了一道“站点管理员审核”动作因为养老服务的服务对象是高龄老人平台要对上门服务的安全负责不能用户下单就自动派单。后面多了一道“已回访”这是社区养老服务用来跟踪满意度的动作也是答辩时的加分项。第二是订单的人维关系。电商订单是“用户—商品”养老服务单是“下单人—老人—服务人员—服务项目”四维结构。下单人不一定是被服务人这种场景在SQL里最直观的体现就是service_appointment表同时有appoint_user_id和elder_id字段前者记录谁下的单后者记录谁接受服务。很多从电商改来的源码只有user_id导致服务记录根本无法对应到具体老人。第三是健康档案不能挂在订单下面。养老平台的核心价值是持续性记录不是一次性交易。我今天上门量了血压下个月又上门测了血糖这两次记录要能汇聚到同一个老人的健康档案页里。所以health_record表必须独立于service_appointment通过elder_id关联而不是作为订单的从表存在。这个设计直接决定家属能不能看到“趋势”而不只是“事件”。2.3 RBAC落地选型Spring Security还是手写拦截器权限控制怎么做是答辩高频问题。基于这个标题的毕业设计背景我见过两条可行路线路线一是 Spring Security JWT用PreAuthorize(hasRole(ADMIN))注解控制接口权限。优点是正规、可扩展缺点是Spring Security的过滤器链对新手不友好一旦配置不对登录接口直接被拦截排查成本很高。路线二是手写拦截器加自定义注解配合JWT做登录态校验。拦截器里解析token拿到用户角色再用HandlerMethod上的注解做角色判断。代码量多几十行但每一步都能讲清楚翻车时也能很快定位。我一般建议毕业设计走路线二原因很实在Spring Security版本和Spring Boot版本不匹配时会出现各种自动装配失效的问题排查一个晚上都很正常。手写拦截器的逻辑是透明的答辩时你可以直接说“我自定义了一个RequiresRole注解在拦截器里做角色匹配”这句话的含金量比“我用了Spring Security”高得多。权限模型确认后下一步就是落地数据库和工程骨架。3. 从建库到SpringBoot最小工程数据库脚本和项目骨架怎么落地很多同学拿到这个标题后习惯先找源码包里的SQL跑一遍表建起来再回头看代码。这个顺序没问题但容易忽略一件事数据库脚本里每一张表为什么存在、字段为什么这么定。下面我会用毕业设计最常见的方案把建库和工程骨架完整梳理一遍。3.1 MySQL核心表设计档案、服务项目、预约单、健康记录社区养老平台的数据库以MySQL为主字符集必须用utf8mb4原因后面避坑章节会说。核心表我一般这样设计。第一张是老人档案表elder_info它是整个系统的地基CREATE TABLE elder_info ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, name varchar(32) NOT NULL COMMENT 老人姓名, gender tinyint DEFAULT NULL COMMENT 性别 0未知 1男 2女, id_card varchar(18) DEFAULT NULL COMMENT 身份证号, phone varchar(20) DEFAULT NULL COMMENT 联系电话, address varchar(255) DEFAULT NULL COMMENT 住址, community_id bigint DEFAULT NULL COMMENT 所属社区ID, emergency_contact varchar(32) DEFAULT NULL COMMENT 紧急联系人, emergency_phone varchar(20) DEFAULT NULL COMMENT 紧急联系电话, status tinyint NOT NULL DEFAULT 1 COMMENT 状态 1正常 0已退住, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_community_status (community_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT老人档案表;community_id用于区分不同社区站点配合status建了一个联合索引。这个索引不是随手加的系统首页最常见的查询是“某个社区状态正常的老人总数”idx_community_status能直接覆盖。第二张是服务项目表service_item定义平台提供哪些服务CREATE TABLE service_item ( id bigint NOT NULL AUTO_INCREMENT, item_name varchar(64) NOT NULL COMMENT 服务项目名称, item_type tinyint NOT NULL COMMENT 类型 1助餐 2助洁 3助医 4康复, price decimal(10,2) DEFAULT 0.00 COMMENT 参考价格0表示免费, duration int DEFAULT 60 COMMENT 预计时长分钟, status tinyint NOT NULL DEFAULT 1 COMMENT 1上架 0下架, create_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT服务项目表;item_type用tinyint做了枚举助餐、助洁、助医、康复四类。不用字符串枚举的好处是数据库存储体积小、查询快坏处是代码里要写常量类或者枚举类把数字翻译成含义。毕业设计里建议写一个ItemTypeEnum答辩时提“我用了枚举来保证类型安全”是一个很自然的亮点。第三张是预约工单表service_appointment这是全系统最核心的表CREATE TABLE service_appointment ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 工单号形如SA20250101001, elder_id bigint NOT NULL COMMENT 被服务老人ID, item_id bigint NOT NULL COMMENT 服务项目ID, appoint_user_id bigint NOT NULL COMMENT 下单人ID可能是家属, service_staff_id bigint DEFAULT NULL COMMENT 服务人员ID派单后写入, appoint_time datetime DEFAULT NULL COMMENT 期望上门时间, address varchar(255) DEFAULT NULL COMMENT 服务地址默认取老人住址, status tinyint NOT NULL DEFAULT 0 COMMENT 0待审核 1待服务 2服务中 3已完成 4已取消 5已回访, remark varchar(255) DEFAULT NULL COMMENT 备注, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_elder_status (elder_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT养老服务预约工单表;order_no设了唯一索引工单号放在业务字段里而不是直接用自增ID是为了后续打印工单、对账、客服查询时好看好念。idx_elder_status覆盖“某个老人所有历史工单”和“某老人当前待服务工单”两个高频查询。第四张是健康记录表elder_health_recordCREATE TABLE elder_health_record ( id bigint NOT NULL AUTO_INCREMENT, elder_id bigint NOT NULL COMMENT 老人ID, record_type tinyint NOT NULL COMMENT 1血压 2血糖 3心率 4体重 5巡诊记录, measure_value varchar(64) DEFAULT NULL COMMENT 测量值如 120/80, record_date date NOT NULL COMMENT 记录日期, doctor_id bigint DEFAULT NULL COMMENT 记录人医护角色ID, advice varchar(500) DEFAULT NULL COMMENT 医嘱或康复建议, create_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_elder_date (elder_id, record_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT老人健康记录表;measure_value用了varchar而不是decimal因为血压值格式是“120/80”心率是纯数字。一个字段要兼容多种记录类型只能用字符串存原始测量值。这是健康医疗场景和普通业务系统差异较大的地方也解释了为什么需要record_type来区分含义。3.2 pom.xml与application.yml一套不容易翻车的版本组合Spring Boot版本是新人踩坑重灾区。我推荐2.7.x系列原因很务实Spring Boot 3.x把javax改成了jakarta很多视频教程和旧源码里的import javax.servlet.*直接编译不过另外MyBatis-Plus对Spring Boot 3的适配starter是单独一套很多同学不知道配错后项目启动直接报错。pom.xml核心依赖这样配parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.2/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesSpring Boot 2.7.18是2.x的最终维护版本稳定且兼容JDK 8和JDK 17。MyBatis-Plus 3.5.3.2对Spring Boot 2.x的自动装配是成熟可靠的不需要额外配置启动类扫描。application.yml里有几个参数值得单独说明server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/community_care?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: mapper-locations: classpath:/mapper/**/*.xml type-aliases-package: com.community.platform.entity configuration: map-underscore-to-camel-case: true global-config: db-config: id-type: autocharacterEncodingutf8解决中文乱码serverTimezoneAsia/Shanghai解决MySQL驱动8.x默认读取UTC时区导致的时间差八小时问题allowPublicKeyRetrievaltrue解决MySQL 8.x密码加密认证方式下的连接报错。最后一个参数很多教程不会提但本地跑MySQL 8.0时经常会遇到。map-underscore-to-camel-case开启后数据库字段create_time能自动映射到实体的createTime属性省掉大量xml里的字段映射。id-type: auto表示主键用数据库自增配合DDL里的AUTO_INCREMENT。3.3 通用返回结构与分页统一先定协议再写接口写接口之前我强烈建议先定义统一返回结构。否则每个接口返回格式都不一样前端对接时每个页面都要单独处理错误提示。Data public class ResultT { private int code; private String msg; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 200; r.msg success; r.data data; return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.code 500; r.msg msg; return r; } }code为200表示成功其他为失败。这套结构可以对齐到前端axios的统一拦截逻辑后端不需要在每个Controller里写try-catch包返回全局异常处理器统一兜底。分页接口用MyBatis-Plus的Page和LambdaQueryWrapper这是全系统最高频的写法GetMapping(/list) public ResultIPageElderInfo pageList( RequestParam(defaultValue 1) long current, RequestParam(defaultValue 10) long size, RequestParam(required false) String keyword, RequestParam(required false) Long communityId) { PageElderInfo page new Page(current, size); LambdaQueryWrapperElderInfo wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(keyword), ElderInfo::getName, keyword) .eq(communityId ! null, ElderInfo::getCommunityId, communityId) .orderByDesc(ElderInfo::getCreateTime); IPageElderInfo result elderInfoService.page(page, wrapper); return Result.ok(result); }LambdaQueryWrapper的好处是方法引用写字段名编译期就能发现拼写错误不用等到运行期看SQL报错。like和eq的第一个参数是条件不成立时该条件自动跳过这样就实现了“keyword传不传都能查”。这段代码在答辩时可以直接说“我用了MyBatis-Plus的条件构造器做动态SQL”属于加分表达。工程骨架能跑起来之后核心业务链路就可以串了。4. 串通核心链路档案查询、预约单流转与健康记录可见性工程骨架和数据库就位后重点落到业务代码。社区养老平台的演示核心就三条线老人档案查得顺手、预约单状态流转严丝合缝、家属看健康档案时数据不越权。4.1 先写实体和Mapper三分钟让MyBatis-Plus接管CRUD实体类直接用Lombok简化Data TableName(elder_info) public class ElderInfo { TableId(type IdType.AUTO) private Long id; private String name; private Integer gender; private String idCard; private String phone; private String address; private Long communityId; private String emergencyContact; private String emergencyPhone; private Integer status; private Date createTime; private Date updateTime; }TableName指定表名TableId(type IdType.AUTO)告知主键策略。字段名createTime通过 yml 里的map-underscore-to-camel-case自动映射到create_time不用写一行xml配置。Mapper接口只做一件关键事Mapper public interface ElderInfoMapper extends BaseMapperElderInfo { }继承BaseMapper后单表CRUD方法全部自带不用写SQL。Service层同样继承IService实现类继承ServiceImpl这是MyBatis-Plus的标准三层写法。4.2 预约单状态流转的接口防线预约工单是业务锚点状态必须由系统控制不能允许前端随意传状态。状态枚举先建出来public enum AppointmentStatus { PENDING(0, 待审核), WAITING_SERVICE(1, 待服务), SERVICING(2, 服务中), FINISHED(3, 已完成), CANCELED(4, 已取消), VISITED(5, 已回访); public final int code; public final String desc; AppointmentStatus(int code, String desc) { this.code code; this.desc desc; } }状态变更接口的核心是一个状态流转校验直接实现状态机private static final MapInteger, SetInteger ALLOW_TRANSITIONS new HashMap(); static { ALLOW_TRANSITIONS.put(0, Set.of(1, 4)); // 待审核 - 待服务 / 已取消 ALLOW_TRANSITIONS.put(1, Set.of(2, 4)); // 待服务 - 服务中 / 已取消 ALLOW_TRANSITIONS.put(2, Set.of(3)); // 服务中 - 已完成 ALLOW_TRANSITIONS.put(3, Set.of(5)); // 已完成 - 已回访 } public ResultVoid changeStatus(Long id, Integer targetStatus) { ServiceAppointment appoint appointmentService.getById(id); if (appoint null) { return Result.error(工单不存在); } SetInteger allowed ALLOW_TRANSITIONS.get(appoint.getStatus()); if (allowed null || !allowed.contains(targetStatus)) { return Result.error(非法状态流转: appoint.getStatus() - targetStatus); } // 派单时写入服务人员ID if (targetStatus AppointmentStatus.WAITING_SERVICE.code) { appoint.setServiceStaffId(CurrentUserHolder.getUserId()); } appoint.setStatus(targetStatus); appointmentService.updateById(appoint); return Result.ok(null); }ALLOW_TRANSITIONS这个Map就是状态机本身。为什么不用switch逐条判断因为状态多了之后switch会越来越长而且“非法流转”的提示不够统一。用邻接表结构的Map新增一个状态只需要加一行答辩讲“我用邻接表实现了状态机”是完整方案不是只贴代码。这里还有一个隐藏细节updateById默认按主键更新字段为空时不会覆盖。但状态变更时前端可能只传targetStatusService里先getById拿到完整对象再改目标字段最后整对象更新避免把其他字段误清空。这是MyBatis-PlusupdateById的坑也是面试常问点。4.3 家属的数据可见性一条不能省的关系过滤家属登录后能看到的老人集合必须受控。先建绑定关系表CREATE TABLE elder_family_rel ( id bigint NOT NULL AUTO_INCREMENT, elder_id bigint NOT NULL, family_user_id bigint NOT NULL, relation varchar(16) DEFAULT NULL COMMENT 父子/母女/其他, PRIMARY KEY (id), KEY idx_family (family_user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT家属与老人绑定关系表;健康记录查询接口里最关键的一行是GetMapping(/records) public ResultListElderHealthRecord elderRecords(Long elderId, Date startDate, Date endDate) { Long userId CurrentUserHolder.getUserId(); if (!familyRelService.isBound(userId, elderId)) { return Result.error(无权查看该老人健康档案); } LambdaQueryWrapperElderHealthRecord wrapper new LambdaQueryWrapper(); wrapper.eq(ElderHealthRecord::getElderId, elderId) .ge(startDate ! null, ElderHealthRecord::getRecordDate, startDate) .le(endDate ! null, ElderHealthRecord::getRecordDate, endDate) .orderByDesc(ElderHealthRecord::getRecordDate); return Result.ok(healthRecordService.list(wrapper)); }isBound方法里查的就是elder_family_rel表是否存在绑定记录。这层校验不能写在SQL里而是放在Service层做业务判断因为“无权查看”需要返回明确错误信息而不是查个空列表让前端猜。答辩时这句话值得讲没有权限控制不是技术水平问题是数据安全问题。到这里核心业务闭环已经通了。下面进入所有SpringBoot项目都会遇到的实战关部署和排错。5. SpringBoot项目运行避坑指南5条高频翻车记录与排查路径这个标题对应的源码包最常见的失败场景不是业务逻辑写错而是环境问题。以下五条是我在跑通同类项目时反复遇到、也帮别人排查过的高频翻车记录每条都按现象、原因、解决三个步骤说清楚。5.1 现象Spring Boot版本太高项目一启动就报组件不存在现象导入源码后编译报cannot find symbol: class ServletContext或者启动时提示ClassNotFoundException: javax.servlet.Filter。原因Spring Boot 3.x把JavaEE的javax命名空间整体迁移到jakarta2.x时代的starter自动装配类很多还挂着javax.servlet两个包名不兼容。源码包如果是按2.x写的用了3.x必挂。解决把Spring Boot版本降到2.7.18。在pom.xml中把parent的version改掉后强制刷新Maven依赖mvn clean mvn dependency:resolve改完后再看依赖树里是否残留spring-boot-starter-parent:3.*mvn dependency:tree -Dincludesorg.springframework.boot如果还有3.x残留说明本地仓库缓存了旧依赖删除~/.m2/repository/org/springframework/boot目录后重新resolve。这个操作要谨慎只在明确确认版本冲突时做。5.2 现象控制台中文乱码数据库里存进去的姓名变成问号现象接口返回的老人姓名是正常中文但前端页面显示乱码或者数据库里存的是???。原因两种情况。一是MySQL连接URL没带characterEncodingutf8驱动用默认latin1字符集做传输二是前端请求头Content-Type没指定charsetUTF-8导致后端接收到乱码再写入库。解决连接URL必须包含完整参数spring: datasource: url: jdbc:mysql://localhost:3306/community_care?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue同时在后端加上编码过滤器兜底Bean public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter new CharacterEncodingFilter(); filter.setEncoding(UTF-8); filter.setForceEncoding(true); return filter; }setForceEncoding(true)的意思是强制请求和响应都使用UTF-8忽略请求头里写的其他编码。这个配置比每个接口手动设置produces application/json;charsetUTF-8干净得多。排查乱码问题时先看数据库里是不是好的如果数据库好、页面坏问题在传输层数据库里就坏问题在写入层。这个顺序能把排查时间缩短一半。5.3 现象Navicat导入SQL脚本报脚本报错或者导完没表现象导入.sql文件时提示Unknown database或You have an error in your SQL syntax。原因多数.sql脚本文件里没有CREATE DATABASE语句默认要求先手动建一个空数据库再执行如果脚本文件又是从Linux环境导出的编码可能是UTF-8无BOMNavicat在Windows下按GBK读第一个表名就乱码了于是报1064语法错。解决统一走命令行导入把编码和建库都显式指定mysql -uroot -p --default-character-setutf8mb4 community_care.sql脚本文件开头手动补上这一段SET NAMES utf8mb4; CREATE DATABASE IF NOT EXISTS community_care DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE community_care;注意字符集必须写utf8mb4而不是utf8。MySQL的utf8实际是utf8mb3不支持四字节的Emoji和生僻字有些家庭成员名字里有生僻字就会存储报错。这是中文环境下一个非常隐蔽的坑。5.4 现象端口被占用服务启动后几秒就自动退出现象控制台显示Port 8080 was already in use.然后Spring Boot进程退出页面访问localhost:8080显示的是一个无关页面。原因本机有别的进程占用了8080。常见元凶是Oracle、微信开发者工具、其他Java进程。解决Windows下用netstat找到占用进程的PID再杀掉netstat -ano | findstr :8080 taskkill /PID 12345 /FLinux和macOS用lsof -i :8080 kill -9 PID如果嫌杀进程麻烦就直接改端口server: port: 8081改完端口记得同步改前端代理地址和本地访问URL。我见过最离谱的一次翻车是同学改了server.port但前端Vue项目里axios的baseURL还写8080整整调试半天。5.5 现象运行时报 Field mapper in ... required a bean of type xxxMapper现象项目启动时提示Field elderInfoMapper in com.community.platform.service.impl.ElderInfoServiceImpl required a bean of type ElderInfoMapper that could not be found。原因Mapper接口没被Spring容器扫描到。常见两种情况Mapper接口上没加Mapper注解或者启动类上的MapperScan包路径写错扫不到实际的Mapper目录。解决优先在启动类上加MapperScanSpringBootApplication MapperScan(com.community.platform.mapper) public class CommunityCareApplication { public static void main(String[] args) { SpringApplication.run(CommunityCareApplication.class, args); } }MapperScan的包路径要和实际存放Mapper接口的包完全一致。源码包解压后如果改了包名这个路径必须跟着改。遇到这个问题时最快定位方法打开IDE的结构树看Mapper接口文件实际在哪个包下复制包路径到MapperScan重新启动。不要靠猜。五条翻车记录每一条都是花时间买回来的经验。项目能稳定运行后想要在答辩或投产演示中脱颖而出还差最后一步把系统做成“会自己说话”的完整平台。6. 从“能跑”到“能演示”定时提醒、统计报表与最终交付验证6.1 用Scheduled实现体检到期提醒给系统加一个“主动”能力演示时能让评委眼前一亮的功能往往不是主流程而是系统每天自动做的事。体检提醒就是个很好的切入点。在Spring Boot里用内置定时任务即可Component public class HealthRemindTask { Scheduled(cron 0 0 8 * * ?) public void remindJudge() { // 查询下次体检日期距今天数小于7天的老人生成提醒记录 } }启动类上记得加EnableScheduling。cron 0 0 8 * * ?表示每天早上八点执行。体检提醒的查询逻辑很简单健康记录表里取每个老人最新的record_date加上体检周期比如一年判断是否在未来七天内到期。跑通后可以在演示时手动改一条记录把体检日期改成明天然后在任务里打个日志观察输出。定时任务放在毕业设计里还有一个好处它能证明你有后端多线程的初步认识。Scheduled默认是单线程串行执行如果前一个任务超时下一个任务会排队。这块在答辩时被问到“你有两个任务同时到点怎么办”可以说“默认串行需要并行可以在配置里加线程池”既体现懂原理又知道边界。6.2 用一张图表撑起系统首页月度服务趋势统计接口答辩现场首页有一张统计图展示系统的数据能力说服力远超一堆列表。用SQL聚合是最直接的做法GetMapping(/statistics/monthly) public ResultListMapString, Object monthlyStatistics() { QueryWrapperServiceAppointment wrapper new QueryWrapper(); wrapper.select(DATE_FORMAT(create_time, %Y-%m) AS month, COUNT(*) AS total, SUM(CASE WHEN status 3 THEN 1 ELSE 0 END) AS finished) .groupBy(month) .orderByAsc(month); return Result.ok(appointmentService.listMaps(wrapper)); }前端用ECharts折线图接这个接口x轴是月份y轴是工单总数和完成数。ECharts接线不复杂核心思路是axios.get(/api/statistics/monthly)拿到data数组然后map成两个数据数组喂给series。这里要注意后端返回的month是2025-01这种字符串排序列名用别名month而不是原字段create_time否则SQL聚合后映射不到列。6.3 交付前的三样检查数据库初始化、README、打包命令不管这个源码包是自用还是交付给老师我都建议打包前固定跑一遍三个检查。第一数据库初始化能不能一键完成。检查SQL脚本是否包含CREATE DATABASE和USE语句不包含的话补上文件编码另存为UTF-8。第二README有没有写清楚环境要求。至少包含JDK版本、MySQL版本、Spring Boot版本、启动步骤。不要写“环境自备”这种话要写“JDK 8或17MySQL 8.0Maven 3.6”。这样别人拿到包后不需要反复确认。第三打包命令固定下来mvn clean package -DskipTests java -jar target/community-care-0.0.1-SNAPSHOT.jar打成jar包后访问localhost:8080用种子账号登录把档案查询、预约流转、健康记录三条链路各走一遍。这个动作看起来简单但在我经手的毕业设计里有三分之一都栽在“IDEA里能跑打包后不能跑”——多数原因是本地代码和打包产物不一致或者资源文件没打进去。-DskipTests跳过的是测试不是编译不要为了图快写成-Dmaven.test.skiptrue。我的习惯是每次交付前把这三步按顺序走一遍先删掉本地数据库重建再按README步骤启动最后用打包产物跑一次全流程。任何一步挂了都说明交付物有缺口。这套习惯救过我很多次今天写在这里希望帮到你。本文还有配套的精品资源点击获取
返回列表