基于Spring Boot构建尿毒症健康管理系统:从架构设计到实践
1. 项目背景与整体设计思路1.1 为什么要做这样一个系统尿毒症不是一个独立的病种而是各种慢性肾脏病发展到终末期的共同结局。患者确诊之后面临的不只是每周两到三次的透析治疗还有长期的饮食控制、用药管理、体征监测和并发症预防。这些事单靠患者自己记靠家属拿纸质本子抄靠医生复诊时翻聊天记录很容易出问题。举个很典型的场景透析患者需要严格控制钾摄入每天吃多少水果、喝多少汤都要心里有数。很多人不是不知道要控钾而是根本没有一个顺手的地方记录今天吃了什么、喝了多少水、体重涨了多少。等到透析时被护士问“这两天上秤多少斤”往往只能凭感觉回答。饮食失控水分摄入过多透析间期体重增长超标轻则影响透析效果重则引发心衰风险。所以做一款尿毒症健康管理系统核心不是炫技术而是把透析记录、饮食管理、用药提醒、体征监测、检验报告这几件事整合到同一个工具里让患者和家属能持续记录让医护人员能远程查看趋势、提前干预。系统解决的是慢病管理里最要命的问题——信息断层。这类系统的典型用户角色有三类患者本人、家属很多时候实际记录的人是家属、医护人员透析中心护士或肾内科医生。三者的使用深度完全不同所以在设计功能模块时要考虑角色差异不能做成一个只有医生才能操作的“病历系统”也不能做成一个只有患者记流水账的“打卡软件”。1.2 技术选型Spring Boot为主框架的理由医疗健康类的管理系统网上能查到很多技术栈方案有基于SSH的老项目有基于Python Flask的轻量应用也有直接套用若依这类后台管理脚手架改的。我最终确定用Spring Boot来做核心原因是下面几点。第一Spring Boot对业务系统的覆盖能力足够完整。尿毒症健康管理系统的本质是CRUD加业务流程Spring Boot的starter机制可以快速集成Web、数据持久化、参数校验、定时任务、消息通知等组件开发效率高生态资料丰富遇到问题基本都能搜到现成方案。第二Spring Boot 3.x之后的版本基于Jakarta EE规范与JDK高版本兼容性好。在JDK 17下运行内存占用和GC表现比旧版本有明显改善。对于要跑在普通服务器甚至云主机上的中小型项目来说这一点很实际。第三团队协作和维护的友好度。这套技术栈国内使用量极大不管是招人还是后面交接给别人接手都没有门槛。不像某些小众框架写的时候很爽出了问题换个同事来看就是灾难现场。第四安全和监控生态成熟。通过spring-boot-starter-actuator可以快速暴露健康检查、指标接口配合Spring Security做权限控制能满足医疗健康类系统对安全性的基本要求。这一点后面单独展开讲因为Actuator如果用不好反而会变成安全漏洞。1.3 四层架构不是照搬概念是解决实际问题网上关于“Spring Boot四层架构”的说法很多其实就是Controller、Service、MapperDAO、EntityModel这四层的经典划分。我一开始觉得这套东西有点笨重写小项目时偶尔会嫌麻烦直接往Controller里堆逻辑但在尿毒症系统这种业务规则较多、字段校验复杂的项目里四层架构的约束价值就体现出来了。各层的定位我总结一下层级核心职责对应的技术载体需要注意的问题Controller层参数接收、请求分发、响应封装Spring MVC注解不要写业务逻辑保持薄Service层业务规则、事务管理、数据组装Service Transactional事务边界要合理不跨层嵌套Mapper/DAO层数据查询与持久化MyBatis-Plus / JPA复杂统计用XML简单CRUD用注解Entity/Model层数据模型映射实体类 DTO/VO数据库字段和VO要分开别混用四层架构最大的好处是出了问题定位快。比如透析记录列表查不出来先看Controller的参数绑定有没有错再查Service层的数据组装最后看SQL条件每一层都可以单独验证。对于需要频繁调整功能的小团队来说这种“模块之间不互相拖累”的结构反而比高度抽象的架构好用得多。我实际开发时还会有个约定Entity对应数据库表结构不直接返回给前端给前端用的统一是VO对象字段名和展示需求对齐。这么做的好处是后面数据库加字段时不会把冗余字段直接暴露给前端避免接口污染。1.4 项目工程结构与依赖管理在动手写代码前工程结构最好一次性理清楚。我常用的Spring Boot项目目录结构是这样规划的src/main/java/com/hms/uremia/ ├── common/ // 通用返回结果、异常处理、工具类 ├── config/ // 配置类WebMvc、CORS、拦截器、定时任务 ├── controller/ // 接口层按业务域拆分 ├── service/ // 业务层接口 impl ├── mapper/ // 数据访问层 ├── entity/ // 数据库实体 ├── dto/ // 入参对象 ├── vo/ // 出参对象 └── task/ // 定时任务依赖方面我用的是Spring Boot 3.x MyBatis-Plus MySQL Redis这套组合。MyBatis-Plus负责单表CRUD的简化操作复杂统计查询用自定义SQL。Redis用来做验证码缓存、Token管理、高频数据的缓存比如患者最近一次体征记录。MySQL负责核心业务数据的持久化存储。如果只是做毕业设计或者小范围内部使用Redis其实可以换成Caffeine本地缓存减少部署复杂度。但考虑到后面可能要接多个客户端同时访问上一套Redis是值得的。2. 核心功能模块与数据库设计2.1 尿毒症健康管理的核心业务拆解在写第一行代码之前先梳理业务域这个步骤比技术选型更重要。尿毒症患者的日常健康管理核心场景可以归纳为六大模块患者档案、透析记录、体征监测、饮食管理、用药提醒、随访计划。患者档案是基础数据包含基本信息年龄、性别、身高、体重、病因原发性肾病类型、透析方式血液透析/腹膜透析、血管通路信息、既往病史。透析记录是高频数据每次透析的日期、时长、脱水量、干体重、透析前后血压等信息都需要记录。体征监测则关注非透析日的体重、血压、尿量对腹膜透析患者尤其重要、体温等指标。饮食管理这块是区别于普通慢病系统的核心功能。尿毒症患者需要限制的不仅是盐还有钾、磷、蛋白质和水。系统的难点在于同一份食物在不同烹饪方式下钾和磷含量差异很大直接做数据库查询只能给参考值关键还要靠规律记录帮助患者形成自我管理习惯。用药提醒则解决“吃了什么”和“该吃什么”的核对问题特别是磷结合剂这类药必须随餐服用才有用记错时间等于白吃。2.2 数据模型设计的关键决策数据库设计上需要先明确表之间的主从关系。患者表是核心主表其余业务表以patient_id为外键关联。比如透析记录表核心字段包括透析日期、透前体重、透后体重、超滤量、透析时长、血流量、透析液流量、抗凝剂用量、并发症情况。每一个字段都对应护士实际填写的内容不能凭空设计。体征记录表和饮食记录表属于典型的高频写入表而且只增不改所以不需要设计update_time字段之外的额外复杂约束。用药提醒表需要关联药品字典表同时记录每次提醒的确认时间和漏服状态方便后续统计依从率。随访计划表则包含计划日期、计划类型、执行状态、随访结论由医护人员创建患者在移动端确认。有一张表的用途容易被忽视——异常记录表。患者体征超标比如血压突然升高或透析间期体重增加过多时系统自动产生一条异常记录推送给绑定的护士或医生同时留痕备查。这张表是后续做数据分析和预警的基础设计时一定要把异常类型、异常值、参考范围、处理状态都加上。2.3 用户体系与权限控制设计尿毒症健康管理系统的用户有明确的等级差异我在设计时采用RBAC基于角色的访问控制模型预置四种角色系统管理员、医生、护士、患者家属归入患者角色。医生查看所有自己管理患者的全部数据创建随访计划查看异常预警。护士维护透析记录录入体征数据管理患者日常护理信息。患者只能看自己的数据记录饮食和体征确认用药提醒。系统管理员不做业务操作只管理部门、用户、字典数据。权限控制的实现不复杂Spring Security JWT足够。要注意的是接口权限颗粒度不能太大也不能太小。比如“透析记录”模块患者只能查看和导出自己的数据护士可以新增和编辑但删除权限只给管理员。这些用一个自定义RequireRole注解加拦截器就能实现不一定非要上复杂的安全框架。2.4 数据库表结构示例建表这块我直接贴一张核心表的建表语句截取透析记录表供大家参考字段设计思路CREATE TABLE dialysis_record ( id bigint(20) NOT NULL AUTO_INCREMENT, patient_id bigint(20) NOT NULL COMMENT 患者ID, dialysis_date date NOT NULL COMMENT 透析日期, dialysis_type tinyint(4) NOT NULL DEFAULT 1 COMMENT 透析方式1-血液透析 2-腹膜透析, pre_weight decimal(5,2) DEFAULT NULL COMMENT 透前体重(kg), post_weight decimal(5,2) DEFAULT NULL COMMENT 透后体重(kg), ultrafiltration decimal(5,2) DEFAULT NULL COMMENT 超滤量(ml), duration int(11) DEFAULT NULL COMMENT 透析时长(分钟), pre_sbp int(11) DEFAULT NULL COMMENT 透前收缩压, pre_dbp int(11) DEFAULT NULL COMMENT 透前舒张压, post_sbp int(11) DEFAULT NULL COMMENT 透后收缩压, post_dbp int(11) DEFAULT NULL COMMENT 透后舒张压, complication varchar(255) DEFAULT NULL COMMENT 并发症记录, remark varchar(500) DEFAULT NULL COMMENT 备注, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_patient_date (patient_id, dialysis_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT透析记录表;联合索引idx_patient_date是重点。因为最常用的查询是“某位患者某段时间内的透析记录”如果只建patient_id的单索引时间范围过滤时走不了最优路径。这个索引在实际数据量大时效果明显不要漏掉。3. 核心功能实现与代码拆解3.1 用户登录与JWT认证实现系统虽然是健康管理工具但涉及患者隐私登录认证必须做扎实。我采用的是Spring Security JWT的方案逻辑不复杂但有几处容易踩坑。JWT的工具类核心思路是登录成功后签发TokenToken里封装用户ID、角色和过期时间后续请求通过拦截器解析Token把用户信息放进ThreadLocal方便业务层随时取用。Service public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; Autowired private StringRedisTemplate redisTemplate; Override public LoginVO login(LoginDTO dto) { // 1. 校验验证码 String code redisTemplate.opsForValue().get(captcha: dto.getUuid()); if (code null || !code.equalsIgnoreCase(dto.getCaptcha())) { throw new BusinessException(验证码错误或已过期); } // 2. 校验用户名密码 User user userMapper.selectByUsername(dto.getUsername()); if (user null || !BCryptUtil.matches(dto.getPassword(), user.getPassword())) { throw new BusinessException(用户名或密码错误); } // 3. 签发JWT String token JwtUtil.createToken(user.getId(), user.getRole(), user.getNickname()); // 4. 记录登录状态 redisTemplate.opsForValue().set(login: user.getId(), token, 24, TimeUnit.HOURS); LoginVO vo new LoginVO(); vo.setToken(token); vo.setUserInfo(convertToVO(user)); return vo; } }密码存储必须用BCrypt加密不要用MD5加盐这种古老方案。BCrypt的校验逻辑自带盐值处理同样的明文每次加密结果不同更安全。另外验证码存入Redis时要设置过期时间一般5分钟就够了。3.2 透析记录模块的完整实现流程透析记录模块是整个系统最核心的高频操作模块由护士在每次透析结束后录入。实现上虽然是普通的增删改查但有几个细节需要注意。第一透前透后体重差可以自动计算超滤量不需要护士手动算。在保存记录时通过pre_weight - post_weight得到大致脱水量不过因为透析过程中患者可能喝水或进食实际超滤量可能比这个差值大所以这个自动计算值只做参考仍允许护士手动调整。第二体重趋势要能联动图表展示。我用了ECharts在前端渲染折线图后端提供一个查询接口返回最近30次透析的日期和体重数据。RestController RequestMapping(/api/dialysis) public class DialysisRecordController { Autowired private DialysisRecordService dialysisRecordService; PostMapping(/save) public Result save(RequestBody Valid DialysisRecordDTO dto) { dialysisRecordService.saveRecord(dto); return Result.success(); } GetMapping(/trend) public Result trend(RequestParam Long patientId, RequestParam(defaultValue 30) Integer days) { ListTrendVO list dialysisRecordService.getWeightTrend(patientId, days); return Result.success(list); } }入库的时候建议用一个DTO接收前端参数通过BeanUtils.copyProperties转成实体再插入。不要直接让前端传Entity否则别人通过接口多传一个id字段就能覆盖已有数据这是很经典的安全漏洞。3.3 体征预警规则的设计与实现体征预警是这类系统区别于普通记账本的重要功能。尿毒症患者的预警规则需要结合临床常识来定义收缩压≥180或≤90时触发紧急预警。舒张压≥110或≤50时触发紧急预警。透析间期体重增长超过干体重的5%触发提醒。体温≥38.5℃时触发感染风险预警。这些阈值不能随便拍脑袋。我参考了肾内科常用管理标准并且把阈值做成了数据库配置项管理员可以调整。预警的实现方式是在体征记录保存时同步在Service层跑一遍规则引擎判断后写入异常记录表再通过WebSocket或短信接口推送。public void checkVitalsAndWarn(VitalSignsRecord record) { ListWarningRule rules warningRuleService.getEnabledRules(); for (WarningRule rule : rules) { boolean hit rule.matches(record); if (hit) { warnRecordService.createWarnRecord(record.getPatientId(), rule, record); notifyService.pushToNurse(record.getPatientId(), rule.getWarningMessage()); } } }生产环境的复杂点在于规则可能互相冲突比如血压又高又低这种不可能的情况代码上用规则表加match表达式来处理前端维护规则页面后端定时刷新规则缓存这样调整阈值就不用重新发版了。3.4 饮食管理模块钾磷摄入控制的实现思路饮食管理模块设计的时候我去查了不少肾病营养学的资料。尿毒症患者饮食管理最看重的是三大指标钾、磷、水分其次是蛋白质和钠。饮食管理的第一个功能是食物查询。维护一张食物成分字典表标注常见食物的钾、磷、蛋白质含量单位为mg/100g或g/100g。用户在录入三餐时可以搜索食物名称选择后输入份量系统自动计算本餐的钾、磷、蛋白质摄入量。第二个功能是一日汇总。按照肾内科标准透析患者每日钾摄入建议控制在2000mg以内磷摄入控制在800-1000mg以内。每天结束时系统汇总当天各项摄入与目标值对比生成提示。第三个功能是“危险食物”提醒。比如杨桃这类对尿毒症患者有明确神经毒性的水果香蕉、橙子等高钾水果动物内脏等高磷食物系统在用户搜索时直接打上红色标签提醒这是细节上最体现专业度的地方。因为食物成分数据用到了多次联表查询和动态条件拼接MyBatis-Plus的Wrapper写起来会有点别扭我直接用XML方式写SQL在script标签里做动态判断。3.5 用药提醒模块定时任务与消息推送用药提醒模块用到Spring Boot的定时任务能力。我先在启动类加上EnableScheduling然后在Task类里写一个定时方法每分钟扫描一次用药计划表如果当前时间落在某条提醒计划的触发时间点且该时间点30分钟内没有确认记录就生成一条待提醒数据推送给用户。核心代码大致这样Component public class MedicationRemindTask { Autowired private MedicationPlanService medicationPlanService; Autowired private NotifyService notifyService; Scheduled(cron 0 * * * * ?) public void scanAndNotify() { LocalDateTime now LocalDateTime.now(); ListMedicationPlan plans medicationPlanService.getPlansNeedRemind(now); for (MedicationPlan plan : plans) { notifyService.sendRemind(plan.getPatientId(), plan.getMedicationName(), plan.getDosage()); medicationPlanService.markReminded(plan.getId(), now); } } }注意用markReminded做幂等标记避免同一分钟多次执行扫描时重复推送。我一开始没有加这个标记测试时发现同一提醒能收到三条消息就是因为定时任务从上一次启动恢复后任务周期之间有重叠执行的情况。3.6 文件上传与检验报告管理检验报告管理模块需要支持患者上传检验单图片护士或医生在后台审核归档。Spring Boot整合文件上传时主要有三个坑文件大小限制、文件存储路径、文件访问映射。application.yml里的配置项示例spring: servlet: multipart: max-file-size: 20MB max-request-size: 50MB文件不要直接存进数据库的BLOB字段除非报告量非常小。我采用的是本地磁盘存储数据库只保存文件路径和元信息同时按照/uploads/patient/{patientId}/{date}/的目录结构存放方便检索。如果未来部署到云服务器这个路径可以直接替换为对象存储比如阿里云OSS或MinIO只需要改一层存储服务接口的实现。文件访问需要配置静态资源映射否则上传后的文件无法通过URL访问Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/uploads/**) .addResourceHandler(file: uploadPath /); } }访问控制上/uploads/**下的文件需要登录才能访问不能直接放开匿名访问。检验报告属于敏感医疗信息我在拦截器里对含有/uploads/的请求加了权限校验只允许本人、绑定的医生护士和研究性管理员访问。3.7 健康趋势分析与统计报表实现系统沉淀了数据之后要给医生提供统计视图。我实现了三个维度的图表接口体重趋势、血压趋势折线图、透析间期体重增长分析柱状图、饮食结构与指标趋势月度汇总。统计查询主要用普通SQL里的聚合函数实现。比如查询最近30次透析记录的体重变化select idselectWeightTrend resultTypecom.hms.uremia.vo.TrendVO SELECT dialysis_date as date, pre_weight as preWeight, post_weight as postWeight, (pre_weight - post_weight) as diff FROM dialysis_record WHERE patient_id #{patientId} ORDER BY dialysis_date DESC LIMIT #{limit} /select实际使用中医生最关心的是“患者近一个月的平均透前血压”和“透析间期体重增长是否超标”这种一眼能看出问题的汇总指标。做报表时不要堆砌指标用最直观的折线图和异常点标注即可。4. 安全设计、监控与部署上线4.1 数据安全与隐私保护实践医疗健康类系统最不能出问题的是数据安全和隐私保护。先从接口层面看必须做统一参数校验避免SQL注入和恶意请求。我封装了统一返回体Result和全局异常处理器RestControllerAdvice所有业务异常统一格式抛出避免异常信息泄露到前端。在Spring Boot中做参数校验推荐使用JSR 303的Valid注解加NotNull、Size、Min这些标注。错误信息不要返回数据库字段名而是返回用户能看懂的中文提示。数据层面身份证号、手机号等敏感字段在数据库里加密存储。我用了AES加解密工具类查询时按需解密不需要明文展示的地方一律脱敏。患者列表页只显示“张*珍”这种脱敏后的姓名点进详情才显示完整信息。权限层面接口全部要求JWT认证并且对患者的查询接口做数据范围校验。比如患者A登录后直接调用/api/dialysis/trend?patientId2想查看别的患者数据必须在Service层确认当前登录用户和请求查询的patient_id匹配否则拒绝。4.2 Actuator监控接入与未授权访问防护Spring Boot Actuator是监控和运维的利器但网上搜“spring boot actuator未授权访问”会发现大量安全事件。很多开发者图方便引入依赖后开着默认配置就上线结果/actuator/env、/actuator/heapdump直接暴露在公网系统变量、内存堆信息全被拖走这就等于把服务器密码写在门口。我接入Actuator时做了三件事防患于未然第一配置只暴露必要的端点默认只开health和info其他的按需打开并通过Spring Security保护management: endpoints: web: exposure: include: health,info,metrics第二所有Actuator端点统一迁移到/admin路径下跟业务路径区分开并配置成仅内网IP访问。第三用Docker部署时通过防火墙限制端口访问。Spring Boot应用本身监听8080端口服务器安全组只放行80/443和SSH端口8080不做公网映射。外部用户访问的是Nginx端口由Nginx反向代理到Spring Boot服务。这样即使暴露了某个端点外网也无法直接探测到。4.3 Spring Boot 4.x与3.x的差异提醒开发时我顺带调研过Spring Boot 4.x的情况。Spring Boot 4.x相比3.x有几个关键变化基线升级到Spring Framework 7和Jakarta EE 11DataSourceAutoConfiguration的包路径发生了变化Jackson的JsonMapper.Builder在4.0.0版本里出现了一些兼容问题部分项目升级后需要显式调整ObjectMapper的配置。如果是新项目直接用Spring Boot 3.x最新稳定版比较靠谱毕竟网上资料、第三方库的兼容性验证都很成熟。如果是老项目升级建议先用spring-boot-maven-plugin的initialize目标检查依赖冲突再逐步升级不要一次大跨步。4.4 基于Docker的部署流程部署方面我整套系统用的是Docker Compose编排。服务器环境为CentOS/Ubuntu云主机安装了Docker和Docker Compose插件。启动配置文件docker-compose.yml的大致结构version: 3.8 services: mysql: image: mysql:8.0 container_name: uremia-mysql environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: uremia_db MYSQL_USER: uremia_user MYSQL_PASSWORD: uremia_pass_2024 volumes: - ./mysql-data:/var/lib/mysql ports: - 3306:3306 networks: - uremia-net redis: image: redis:7-alpine container_name: uremia-redis ports: - 6379:6379 networks: - uremia-net app: build: . container_name: uremia-app depends_on: - mysql - redis environment: SPRING_PROFILES_ACTIVE: prod SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/uremia_db SPRING_DATA_REDIS_HOST: redis ports: - 8080:8080 networks: - uremia-net networks: uremia-net: driver: bridgeDocker部署有几个细节要特别留意。MySQL的数据库文件目录必须挂载到宿主机否则容器删掉数据全没。容器内访问MySQL和Redis时不要用localhost要用服务名mysql、redis因为容器间通信走的是一个隔离网络。还有时区问题Docker容器默认是UTC时间要在环境变量里加上TZAsia/Shanghai否则定时任务和日期记录会差8个小时。4.5 HTTPS访问配置健康管理系统收集的都是患者的真实健康数据上线后必须配置HTTPS。我的做法是在云服务商申请免费SSL证书配置到Nginx上让Spring Boot应用跑在内网Nginx负责HTTPS的终止和反向代理。Nginx的server块配置示例server { listen 443 ssl; server_name health.example.com; ssl_certificate /etc/nginx/ssl/health.example.com.pem; ssl_certificate_key /etc/nginx/ssl/health.example.com.key; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }配完证书后用在线工具检测一下HTTPS是否生效重点看证书链是否完整。实际踩过的坑是只配了443的server块没配80端口的跳转导致用户输入HTTP网址时无法访问。正确的做法是再加一个80端口server块用return 301 https://$host$request_uri;将HTTP请求永久重定向到HTTPS。5. 常见问题与排查实录5.1 典型问题速查表整个开发部署过程中我把最有代表性的问题整理成了一张速查表这里直接分享出来帮大家省排查时间问题现象可能原因排查方法上传的文件返回404静态资源映射路径配置错误检查addResourceHandlers中的file:路径是否带上了末尾斜杠定时任务不执行忘记加EnableScheduling检查启动类注解确认spring-boot-starter依赖存在Redis连接超时服务器安全组未放行6379端口或Redis绑定IP配置错误检查redis.conf的bind配置和防火墙规则中文乱码数据库连接URL缺少characterEncodingutf8检查jdbc:mysql://连接串参数是否完整JWT Token过期后提示不友好全局异常未捕获ExpiredJwtException在JWT拦截器中单独捕获返回401并提示重新登录接口返回数据比预期多几个字段VO对象中的字段没有手动设置值直接返回了Entity统一使用VO对象避免直接返回实体类5.2 印象最深的三个坑第一个坑是关于MyBatis-Plus的字段自动填充。我在实体类里定义了createTime和updateTime以为交给数据库的DEFAULT CURRENT_TIMESTAMP处理就行。结果插入数据时MyBatis-Plus默认会插入实体对象里的字段如果实体对象里这两个字段为nullSQL会显式插入NULL把数据库的默认值覆盖掉导致创建时间为空。解决办法是使用TableField(fill FieldFill.INSERT)配合MetaObjectHandler在插入时自动填充或者把实体里这两个字段标记为insertStrategy FieldStrategy.NEVER。第二个坑是跨域配置。前端和后端分开部署时浏览器会拦截跨域请求。我们在后端写了CORS配置类放行了所有来源和请求头。上线后没有出现问题但在对接微信小程序时发现微信小程序环境下CORS不生效后端已经出现OPTIONS请求返回403的情况。排查发现是网关或Nginx层拦截了OPTIONS预检请求没有透传给后端。解决方法是专门为OPTIONS请求返回204状态码或者在Nginx配置里放行预检请求。第三个坑是Actuator的health端点默认会检查所有依赖服务的状态。如果Redis连接不上/actuator/health会返回DOWN状态K8s或云监控会根据这个状态把应用判为不健康从而重新拉取容器。这本身是合理的但就是有一次Redis服务器重启维护期间应用被反复重启场面很狼狈。解决方式是把Redis的health检查状态设置为不参与整体健康判定management: health: redis: enabled: true这里其实踩了一个反直觉的点enabled: true只表示开启Redis健康检查指标的展示但不会影响整体的UP/DOWN。真的确定要“Redis挂了应用不重启”可以在HealthContributorRegistry里自定义一个忽略Redis状态的健康指示器。踩过一次之后就明白了监控配置不是默认就好必须想清楚每个配置的连带影响。5.3 性能优化心得健康管理系统的并发量不高但数据查询有一些常见瓶颈。我做过的几个关键优化可以分为两类。索引优化上高频查询的WHERE条件字段都要有索引。透析记录表和高频关系表的数据量增长快没有(patient_id, dialysis_date)联合索引时查询日期范围会走全表扫描慢的时候超过两秒。缓存优化上短期内不变的数据用Redis缓存。比如食物成分字典表基本不会变查询量大就用Cacheable注解做缓存key设置食物的名称前缀过期时间设为一天。患者最近一次体征记录也适合缓存因为首页展示每次都要查但实际数据只有最新一条需要展示。用Redis存一个last:vitals:{patientId}的key更新时写入读的时候优先走缓存。5.4 给新手的几点实战建议第一个建议不要一开始就追求微服务架构。尿毒症健康管理系统的业务量级单体Spring Boot应用完全够用非要拆成网关、用户服务、业务服务、消息服务反而把代码复杂度和部署成本拉高了。第二个建议先画原型图再写代码。这类系统用户界面直接影响使用体验患者端的页面逻辑要足够简单年龄大的患者可能连“侧边栏”是什么都不知道。功能键要放在显眼位置字体要可以调大。读项目代码的开发者可能对这些不敏感但实际使用者是患者不是程序员。第三个建议一定要做好数据备份。医疗数据丢不起我上线后每天凌晨通过crontab任务备份MySQL备份文件保留30天。这一个月里没出过问题但备份脚本起了作用——有一次手动操作失误把一张表的数据清掉了直接从备份恢复十分钟搞定惊出一身冷汗。第四个建议预留扩展能力。尿毒症患者的随访管理、透析中心排班管理、护士工作台等功能虽然没有包含在第一版但数据库设计和接口设计都预留了字段和接口位置。后面接第三方随访系统或对接透析机数据时不需要推倒重来。6. 写在最后的几点想法这个系统从设计到落地前后花了大概两个月。回头整理这套方案时我最大的感受是技术本身不是难点真正花时间的是理解和还原真实的业务场景。比如透析间期体重增长这件事刚开始做体重趋势图时我认为折线图就够用了。后来跟肾内科护士聊了两次才明白对护士来说她们最关注的不是某天体重多少而是一周里体重涨了多少、两次透析之间是否超过了干体重的3%-5%。于是我在图表上增加了参考阈值线和超标标识护士扫一眼就能判断风险体验比之前好太多。饮食管理中钾磷控制模块也是同理。仅仅提供食物成分查询用户根本不知道怎么用。后来我在设计上增加了“今天已摄入”的汇总条用户录完早餐就能看到钾和磷还剩多少配额。数值是冷冰冰的但加上进度提醒后用户会更有动力记录。技术方案本身并不花哨Spring Boot加MyBatis-Plus加MySQL加Redis的组合在现在的开发环境下属于非常“标准”的答案。但医疗健康场景的特殊性在于每一个设计取舍背后都关系到真实的人。系统的价值不只是CRUD完成了而是患者真的通过它养成了记录体重和饮食的习惯护士通过它减少了电话回访的工作量医生通过趋势图更快地发现患者状态的变化。做这类项目我一直提醒自己一个原则忘掉技术名词先回答“用户到底需要什么”。回答了这个问题技术选型和代码实现就只是顺手的事。希望这篇记录能帮你少走一些弯路。