SpringCloud智慧养老平台微服务拆分与部署实践

发布时间:2026/9/17 15:01:57
SpringCloud智慧养老平台微服务拆分与部署实践 简介一套基于SpringCloud的智慧养老平台完整项目面向Java开发者、微服务学习者及毕业设计使用者包含前后端源码、数据库脚本、配套文档和答辩PPT。资源包共953个文件压缩后约29.52MB以Java后端、Vue前端、HTML/JS/CSS静态资源、SQL数据库脚本及项目文档为主并附带安装/启动批处理脚本便于本地部署调试。系统按管理员和老人两类角色设计管理员侧覆盖老人档案、亲属关系、每日健康、既往病史、活动与商品管理、服务购买、紧急求助、礼品发放及积分等业务老人端则提供养老平台、电影信息与后台管理模块。项目采用SpringCloud微服务架构与B/S模式具有资源占用低、响应迅速、界面简洁易上手的特性适合理解微服务拆分、权限管理及实际业务建模。目前已有114人学习下载配套文档与PPT便于梳理设计思路和功能演示是一份完整度较高的实战型学习资料。1. SpringCloud在智慧养老平台里解决的不只是微服务还有交付边界以SpringCloud做智慧养老平台交付物通常是一套源代码、一个初始化数据库脚本、一份设计文档和答辩PPT。技术难点不在启动类写得多标准而在服务和数据表之间怎么切边界老人档案、健康监测、护理工单、费用补贴这几块数据关联强更新频率却完全不同。健康设备一天上报几万条档案一天改不了几次做成单体后改档案的人和改上报逻辑的人会互相挡路。SpringCloud在这里解决的就是把能独立部署的服务拆开各连各的表再用注册中心和网关把调用收敛起来。下面按实际交付的顺序从拆分、建表、写查询到部署排错把能直接抄的配置和代码给出来。2. SpringCloud微服务拆分与最小可运行骨架先切边界再动代码2.1 按业务角色而不是按数据表拆服务养老场景才不会拆出循环依赖很多课程设计拿到“智慧养老平台”就按表拆分老人表一个服务床位表一个服务健康记录一个服务。这样拆出来的服务粒度等于数据表服务之间的调用会变成网状——查一个老人详情要同时调三个服务每个服务都直连数据库。我一般按调用方角色拆四个auth-service 负责登录注册和 Token 签发elder-service 负责老人档案、床位和家属绑定monitor-service 接收健康设备数据并提供趋势查询order-service 处理护理工单和费用结算。这里的关键是老人档案和健康记录不在同一个服务里因为写入频率和事务边界不同而工单和结算放一起因为一次护理完成要同时改两边。为什么用 SpringCloud 而不是 Spring Boot 多模块因为养老平台往往要接不同院区的独立部署或者后续要把健康监测单独卖给第三方。服务拆开后网关统一走 8000 端口外部只认识网关不知道后面有几个服务在跑。因此在骨架阶段就要把注册中心、网关、两个以上业务服务搭起来服务发现、网关路由、远程调用、负载均衡和配置管理这五个基础组件才算凑齐。2.2 先启动Nacos再启动业务服务注册中心是整个平台的地基开发期我建议用 Nacos 做注册中心兼配置中心版本选 2.x。启动前修改 conf/application.properties 里的数据源配置把 Nacos 的存储从内置 Derby 换成 MySQL这样重启后配置不丢。生产环境不要用 Derby多节点 Nacos 必须共用同一个 MySQL否则各节点看到的服务列表会不一致。启动命令在 Windows 下是startup.cmd -m standaloneLinux 下是sh startup.sh -m standalone。验证方式很简单浏览器打开http://localhost:8848/nacos默认用户名密码都是 nacos进入服务管理能看到注册上来的服务。注意 Nacos 2.x 需要开放 8848 和 9848 两个端口9848 是 gRPC 通信端口只开 8848 会导致服务发心跳间歇性失败。2.2.1 网关路由表与跨域配置网关用 spring-cloud-starter-gateway路由配置写进 bootstrap.yml。下面是一份常用的最小配置server: port: 8000 spring: application: name: gateway cloud: nacos: server-addr: localhost:8848 gateway: routes: # Path 匹配到的请求转发给 elder-service - id: elder-route uri: lb://elder-service predicates: - Path/api/elder/** - id: monitor-route uri: lb://monitor-service predicates: - Path/api/monitor/** filters: - StripPrefix1逻辑说明uri 里的lb://表示从 Nacos 拿服务实例做负载均衡这个前缀不是可选项SpringCloud 集成 LoadBalancer 后靠它识别服务名。Path 断言把所有以/api/elder/开头的请求转发给 elder-service。这里容易踩的坑是 StripPrefix 的使用如果服务端接口路径写的是/elder/list而网关收到的请求是/api/elder/list需要配 StripPrefix1 把第一段/api剥掉但如果服务端已经习惯写全路径这段过滤器会多剥一层导致 404。我习惯在网关层统一剥掉版本前缀业务服务里不再关心对外路径。跨域配置放在网关而不是每个服务因为浏览器直接请求的是网关地址。在 GatewayConfig 里加一个 CorsWebFilterallowedOriginPatterns 配前端地址或*开发期allowedMethods 配 GET, POST, PUT, DELETE, OPTIONS。OPTIONS 必须放行否则前端的预检请求会被网关拦掉表现就是接口在 Postman 正常、浏览器里报跨域。2.3 四个服务的POM依赖清单版本锁定比全部最新更重要服务多了以后各模块的 Spring Cloud 版本不一致是启动期最常见的报错来源。我用下面的版本组合在一套 Java 8 环境里稳定跑通Java 11 也可以组件版本Spring Boot2.6.13Spring Cloud2021.0.5Spring Cloud Alibaba2021.0.5.0Nacos Server2.2.3选这个组合的理由Spring Cloud Alibaba 2021.0.5.0 官方 BOM 对应的就是 Boot 2.6.x 和 Cloud 2021.0.x版本对应关系最稳。与其追 springcloud alibaba 2025.0 这种新版本不如先跑通骨架再考虑升级。父 POM 里用 dependencyManagement 锁定版本子模块不要写版本号dependencyManagement dependencies dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-dependencies/artifactId version2021.0.5/version typepom/type scopeimport/scope /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-alibaba-dependencies/artifactId version2021.0.5.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement逻辑说明spring-cloud-dependencies 和 spring-cloud-alibaba-dependencies 必须同时引入因为 Nacos 发现、Nacos 配置和 Sentinel 的版本都由 Alibaba 统一管理。如果只引 Spring Cloud 官方依赖nacos-discovery 会报找不到 LoadBalancer 的类。子服务里还要加 spring-cloud-starter-bootstrap 依赖否则 bootstrap.yml 不会被加载Nacos 地址读不到服务直接启动失败。提示启动 Nacos 前先执行java -version确认是 Java 8Nacos 2.2.3 对 JDK 17 的时钟偏移和模块化参数处理需要额外配置毕设环境里没必要给自己加难度。3. 养老核心模块的数据库设计与增删改查从表结构到接口可用3.1 表结构设计的四个关键表字段与索引选择智慧养老平台的数据库设计不能只考虑存取还要为答辩和后续统计准备。下面是在 elder-service 里用得比较顺的四张表表名核心字段关键索引elder_infoid, name, id_card, phone, room_id, status, family_phoneuk_id_card 唯一索引status 普通索引bed_infoid, room_no, bed_no, elder_id, statusuk_bed_no 唯一索引elder_id 普通索引health_recordid, elder_id, heart_rate, blood_pressure, spo2, recorded_atidx_elder_time(elder_id, recorded_at) 联合索引care_orderid, elder_id, worker_id, order_type, amount, status, created_atidx_status_time(status, created_at) 联合索引这里值得展开的是 health_record。健康设备每分钟上报一次的话一张床一天 1440 条一个月就会到百万量级。把 elder_id 和 recorded_at 建成联合索引是因为查询模式永远都是“查某个老人某段时间的曲线”而且这个查询频率远高于设备上报频率。数据库课程设计答辩时被问到“大数据量怎么优化”答联合索引和分表策略比答缓存更实际。care_order 里不要把床位号和护理内容冗余进去而是通过 elder_id 关联 elder_info。因为在养老系统里老人换床是常见操作如果工单表冗余了床号换床后历史工单的床号就错了。维护关联字段而不做冗余是这里最重要的设计取舍。3.2 用MyBatis-Plus实现老人档案的条件分页查询拿到智慧养老平台源代码时我一般先从 mapper 和 service 两层看起而不是先看 Controller。Controller 无非是接收参数然后调 service真正决定功能能不能跑的是查询构造和事务边界。elder-service 使用 MyBatis-Plusxml 都不用写条件构造器就够了。下面是查询接口的核心代码GetMapping(/elder/page) public ResultIPageElderInfo page(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String keyword, RequestParam(required false) Integer status) { LambdaQueryWrapperElderInfo wrapper new LambdaQueryWrapper(); // keyword 有内容时才拼接模糊查询条件 wrapper.like(StringUtils.hasText(keyword), ElderInfo::getName, keyword); // status 为空时不拼等于条件 wrapper.eq(status ! null, ElderInfo::getStatus, status); wrapper.orderByDesc(ElderInfo::getCreateTime); IPageElderInfo page elderInfoMapper.selectPage( new Page(pageNum, pageSize), wrapper); return Result.success(page); }逻辑说明LambdaQueryWrapper 用方法引用代替字符串列名编译期就能检查字段名拼写不会出现“elderName 写错导致 SQL 运行时报错”的情况。like 和 eq 的第一个 boolean 参数表示条件是否生效keyword 为空时就不拼这个条件status 为空时不拼等于条件Service 层可以少写一串 if 判断。selectPage 传入 Page 对象后MyBatis-Plus 会自动带上 LIMIT 并统计总记录数。注意 Page 对象默认会执行 count 查询数据量大时这一步会拖慢接口如果前端滚动加载不需要总数在 Page 对象上 setSearchCount(false) 关掉。这里用 Spring 的StringUtils.hasText而不是!StringUtils.isEmpty因为 hasText 对纯空格字符串也返回 false避免用户输入几个空格就去数据库做全表模糊匹配。3.3 统一返回体与全局异常给网关和前端一个稳定的协议四个服务之间互相调用如果每个服务返回的 JSON 结构不一样Feign 的反序列化会非常痛苦。我定义了一个 Result 类code 固定为 0 表示成功非 0 为业务错误码message 给前端直接展示data 放结果public class ResultT { private int code; // 0 表示成功非 0 为业务错误码 private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 0; r.message ok; r.data data; return r; } public static T ResultT error(int code, String msg) { ResultT r new Result(); r.code code; r.message msg; return r; } }逻辑说明success 和 error 分别用于成功与失败分支业务层只需要return Result.success(...)或return Result.error(1001, 该老人已被绑定)不需要每次手动构造对象。再配一个RestControllerAdvice的全局异常处理把自定义业务异常和兜底的 Exception 分开处理老人已被绑定返回 1001数据库唯一键冲突返回 1002未知异常返回 500 并记录日志。这样做的目的是让网关的全局过滤器能统一识别业务错误不需要每个服务各自 catch 一遍。凡是用 SpringCloud 做多服务协作响应体协议必须先定好否则联调到一半改协议会牵扯所有服务。4. 本地与服务器的SpringCloud部署从Nacos启动到三个必查报错4.1 Windows和Linux下Nacos的启动参数与端口放行开发时在 Windows 上启动 Nacos生产放到 Linux 服务器两个环境的差异主要在内存设置和防火墙。开发机默认 JVM 参数是 1G如果电脑只有 8G 内存同时跑四个服务加 Nacos 会卡。修改 startup.sh 里的 JVM 参数为 512m# Linux 下修改 nacos/bin/startup.sh 中 JVM 参数 JAVA_OPT${JAVA_OPT} -Xms512m -Xmx512m -Xmn256m # 启动standalone 单机模式不要用集群模式 sh startup.sh -m standalone生产环境虽然可以用 standalone但 Nacos 挂掉后已实例可以通过本地缓存继续处理请求新服务却注册不上。如果这是院区级别的系统建议至少两个 Nacos 节点组成集群配合 MySQL 存储配置和注册数据。单机 standalone 适合课程设计与演示环境不要拿它当生产高可用方案。Linux 服务器上要放行端口否则服务注册不上或页面打不开firewall-cmd --permanent --add-port8848/tcp firewall-cmd --permanent --add-port9848/tcp firewall-cmd --permanent --add-port8000/tcp firewall-cmd --reload9848 端口是 Nacos 2.x 新增的 gRPC 通信端口初学者漏掉它会出现一种很隐蔽的现象Nacos 控制台能看到服务但服务之间的 OpenFeign 调用偶尔超时因为客户端连不上 gRPC 通道后不断重试。4.2 服务打包顺序与启动脚本先注册中心再业务服务四个服务的端口要固定下来部署文档里写清楚避免随机端口导致网关路由和防火墙配置混乱服务端口是否注册到 Nacos关键依赖gateway8000是所有业务服务auth-service8010是MySQLelder-service8011是MySQLmonitor-service8012是MySQLorder-service8013是MySQL打包命令在父工程根目录执行# 跳过测试打包避免单元测试连不上数据库导致构建失败 mvn clean package -DskipTests # 在服务器上启动服务以 elder-service 为例 nohup java -jar elder-service/target/elder-service-1.0.0.jar \ --spring.cloud.nacos.server-addr192.168.1.10:8848 \ --server.port8011 logs/elder.log 21 这里把 Nacos 地址通过命令行参数覆盖 bootstrap.yml 里的 localhost:8848这样开发环境打出来的 jar 不用重新构建就能部署到服务器上。server.port 也可以用命令行覆盖方便在同一台机器上起多实例做负载均衡演示。日志输出重定向到 logs/elder.log排查问题看这个文件而不是看控制台。4.3 三个最容易出现的部署报错与排查手段第一服务启动后在 Nacos 控制台看不到。先看日志里有没有 “nacos registry register” 字样如果没有检查 bootstrap.yml 是否生效。Spring Boot 2.4 以后默认不加载 bootstrap.yml必须在 POM 里加 spring-cloud-starter-bootstrap否则 Nacos 地址读不到。还有一个隐蔽情况pom 里同时残留了 eureka-client 和 nacos-discovery两个注册中心会同时尝试注册造成服务列表时有时无检查依赖树里有没有旧版本的 eureka 依赖。第二gateway 转发报 503 Service Unavailable。打开网关日志或直接访问服务实例地址确认服务本身活着然后确认路由里的 uri 写的是 lb://服务名服务名必须和 spring.application.name 完全一致包括大小写和连字符。Nacos 控制台显示的服务名就是 uri 要填的名字不要凭印象写。第三数据库连接超时。Spring Boot 默认 HikariCP 的 connectionTimeout 是 30 秒云服务器之间网络抖动会直接抛 Connection is not available 异常。如果业务高峰时出现大量这类日志在 application.yml 里调大参数spring: datasource: hikari: connection-timeout: 60000 maximum-pool-size: 20 connection-test-query: SELECT 1连接池大小不是越大越好超过 MySQL 的 max_connections 会让数据库本身变慢。20 个连接足够一个养老院级别的服务使用如果报表统计并发特别高应该单独给报表服务配一个数据源而不是所有服务连同一个库。数据库连接串建议加上 useSSLfalse 和 serverTimezoneAsia/Shanghai否则高版本 MySQL 驱动会报时区错误或 SSL 握手失败。5. 把OpenFeign超时、重试与降级调好健康上报不丢数据健康设备上报数据后monitor-service 要调用 elder-service 确认老人在住状态顺便写入一条提醒记录。这两步之间如果没有超时和重试设备上报高峰期很容易出现部分数据静默丢失——接口返回 200但数据没落库。Spring Cloud 2021 以后 Ribbon 被移除OpenFeign 的超时参数要写在spring.cloud.openfeign.client.config下。我实际用下来的配置是spring: cloud: openfeign: client: config: default: connectTimeout: 3000 readTimeout: 5000 elder-service: connectTimeout: 2000 readTimeout: 10000配置里 default 对所有服务生效elder-service 单独覆盖读超时因为健康趋势查询可能超过默认 5 秒。connectTimeout 是建立连接的时间readTimeout 是等响应的时间两个别混了。之前见过有人把 readTimeout 写到 connectTimeout 上接口一慢就报 SocketTimeoutException其实是读超时触发。超时之外还要看重试。Feign 默认不重试需要开启并限制次数spring: cloud: openfeign: retry: enabled: true重试有个前提只有接口是幂等的才能开。养老场景下查询类接口和“更新状态为已通知”这类接口可以重试扣费、生成工单这种写操作不能盲目重试否则会造成重复单据。我的做法是给写接口的 Feign 方法单独关闭重试通过不同 FeignClient 配置区分而不是全局开关一把梭。最后是降级。开启feign.circuitbreaker.enabled后Feign 的 fallback 才能生效。我习惯在 FeignClient 注解里指定 fallback 类FeignClient(name elder-service, fallback ElderClientFallback.class) public interface ElderClient { GetMapping(/elder/{id}) ResultElderInfo getElder(PathVariable(id) Long id); }fallback 类里返回一个降级提示的 Result比如 code 为 503、message 为“服务繁忙”不要返回 null。上游拿到 null 后如果直接空指针前端看到的错误会更难懂。配合 Sentinel 做限流是进阶做法但先把超时、重试、降级这三层调好体检报告推送的可用性已经能覆盖绝大多数场景。调完用 curl 连续请求健康上报接口观察 monitor-service 日志里的 Feign 调用耗时超过 readTimeout 的部分就是需要优化的目标。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询