拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Spring Cloud药品管理系统:微服务拆分、注册发现与熔断降级实战

简介基于Spring Cloud构建的综合药品管理系统毕业设计源码适合计算机相关专业学生、微服务入门开发者及药品信息化项目参考者使用。项目围绕药品入库、出库、库存管理、供应商管理等核心业务通过服务注册发现、API网关、配置中心等Spring Cloud组件实现分布式架构并配合Vue前端完成前后端分离设计覆盖了微服务开发全链路。资源共1027个文件压缩包7.23MB其中375个Java文件承载业务逻辑与微服务代码77个Vue及167个JS文件支撑管理后台界面另含SQL数据库脚本、YML配置、前端构建配置等目录结构清晰便于按模块研读。目前已有207人学习下载适合用于毕业设计选题、微服务项目实训或二次开发蓝本。通过该项目可同时掌握服务拆分、接口调用、权限控制及前后端联调等关键技能是一份完整度较高的工程化学习资料。1. 毕业设计里的 Spring Cloud 药品系统为什么值得拆开看很多人在做基于 Spring Cloud 的综合管理系统时最容易踩的坑不是业务逻辑而是服务拆完了不知道怎么合起来。这套基于 Spring Cloud 构建的综合药品管理系统源代码把药品入库、出库、库存、供应商和销售记录拆成了独立微服务再用注册中心、配置中心、网关和声明式调用把它们串成一条完整链路。它不是那种单模块改个包名就交差的“伪微服务”而是把服务注册与发现、远程调用、路由转发、熔断降级都落到了代码里。对正在做毕业设计、或者想从单体跳向微服务的人来说源码里最有价值的部分不是 CRUD 本身而是服务边界怎么划、配置怎么分层、调用链怎么串起来。2. 微服务拆分与服务注册发现先把药的“进销存”切成服务2.1 为什么药品管理系统适合用 Spring Cloud 拆服务药品管理系统的业务域天然适合微服务化。药品主数据、库存流水、采购订单、销售出库、供应商档案这几个模块的变更频率和数据量差异很大。比如库存模块每天有大量流水写入而供应商模块可能一周才改几次。如果全部塞进一个单体应用一次库存并发高峰会把供应商查询也拖垮。拆成独立服务之后每个服务可以独立扩实例、独立发版互不干扰。这套源码的服务划分基本遵循按业务域拆分的思路。我一般会看 distribution 模块里的业务边界常见的划分方式是按 domain 分包比如medicine-service、stock-service、purchase-service、sale-service、supplier-service每个服务独立数据库。这种拆分方式的好处是后续如果要接分布式事务或者做读写分离边界是天然的。2.2 medicine-master 目录结构与服务定位拿到源码包解压之后是medicine-master根目录。先不要急着启动先把目录结构过一遍确认每个模块的职责。一般 Spring Cloud 项目的根目录下会有多个子模块父 pom 统一管理依赖版本子模块之间通过 groupId 和 artifactId 互相引用。medicine-master ├── pom.xml # 父 POM统一版本管理 ├── eureka-server # 注册中心 ├── config-server # 配置中心 ├── api-gateway # 网关服务 ├── medicine-service # 药品基础信息服务 ├── stock-service # 库存服务 ├── purchase-service # 采购服务 ├── sale-service # 销售服务 └── supplier-service # 供应商服务启动顺序很重要。必须先启动 eureka-server再启动 config-server然后是 api-gateway最后才是业务服务。如果业务服务先启动Eureka 客户端会一直报连接 refused虽然 Spring Cloud 有重试机制但第一次启动时的报错会干扰判断。父 POM 里一般会锁定 Spring Boot 和 Spring Cloud 的版本对应关系比如 Spring Boot 2.3.x 配 Spring Cloud Hoxton.SR8这点不要随便改升级版本时容易出现组件兼容性问题。2.3 Eureka Server 注册中心配置与启动验证注册中心是微服务架构的“通讯录”。Eureka Server 本身也是一个 Spring Boot 应用只是开启了服务端模式。它的核心配置在application.yml里关键就三个参数服务端口、是否注册自己、是否拉取注册表。server: port: 8761 eureka: instance: hostname: localhost client: register-with-eureka: false fetch-registry: false server: enable-self-preservation: false在单机开发环境下register-with-eureka和fetch-registry必须设为 false否则 Eureka Server 会尝试把自己注册到注册中心控制台会一直刷警告日志。enable-self-preservation是自我保护机制生产环境一般开着防止网络分区时误删实例但开发调试时不关的话服务下线后注册表里还会残留旧实例排查问题时容易被误导。启动后访问http://localhost:8761能看到 Spring Eureka 的管理页面。页面里的Instances currently registered with Eureka表格列出了所有注册上来的服务实例。服务显示的名称取的是spring.application.name这个配置项必须在每个业务服务里显式定义否则注册中心里会显示一堆UNKNOWN后面网关路由也没法按服务名转发。3. 配置中心与网关路由把“改配置要重启”这件事解决掉3.1 配置中心为什么要单独拆一个服务微服务拆开之后每个服务都有自己的一套配置。数据库连接、Redis 地址、日志级别、开关项这些配置散落在各个服务里改一个数据库密码要挨个改。Spring Cloud Config 解决的就是这个问题把配置集中到一个仓库里统一管理服务启动时从配置中心拉取。这套源码里的 config-server 走的是 native 模式也就是配置文件直接放在 config-server 自己的 classpath 或本地目录下。生产上更常见的是把配置放到 Git 仓库配合 Spring Cloud Bus 实现配置动态刷新但毕业设计用 native 模式足够重点是理解配置分层加载的顺序。3.2 bootstrap.yml 与配置拉取机制每个业务服务里都有两个配置文件bootstrap.yml和application.yml。bootstrap.yml 的优先级更高它先于 application.yml 加载用来连接配置中心。业务服务启动时先读 bootstrap.yml从中拿到 config-server 的地址和服务名然后去配置中心拉取对应的配置文件。# bootstrap.yml spring: application: name: stock-service cloud: config: uri: http://localhost:8888 fail-fast: truefail-fast参数值得说一下。如果配置中心连不上默认情况下服务会继续启动但配置项为空运行时各种 NullPointerException。设成 true 之后连接失败直接启动失败错误信息一下子就暴露出来了。调试阶段建议打开出问题早暴露比晚暴露好。3.3 API Gateway 路由与过滤器配置网关是流量的统一入口。前端不直接调各个微服务而是把请求发给网关网关按路径规则把请求转发到对应的服务。这套源码用的网关实现需要看依赖里是哪个版本——如果是 Spring Cloud Hoxton 之前用的是 Zuul之后基本都是 Spring Cloud Gateway基于 WebFlux性能更好。以 Spring Cloud Gateway 为例路由配置的核心是spring.cloud.gateway.routes列表。每个路由有 id、uri、predicates 和 filters。uri 用lb://前缀表示走负载均衡后面跟的是注册中心里的服务名网关会自动从 Eureka 拿到服务实例列表再用轮询或随机策略做负载均衡。spring: cloud: gateway: routes: - id: medicine-route uri: lb://medicine-service predicates: - Path/api/medicine/** filters: - StripPrefix1StripPrefix1的意思是去掉路径里的第一个前缀再转发。前端请求/api/medicine/list网关去掉/api转发给 medicine-service 的/medicine/list。这里容易出错的地方是路径前缀不一致。比如服务内部的 Controller 映射是/medicine/list网关 routes 里配置的 Path 是/api/medicine/**如果忘记 StripPrefix转发过去就变成/api/medicine/list直接 404。网关还有个常见的关注点就是跨域。前后端分离项目里前端跑在 8080网关跑在 8080 之外的其他端口浏览器会拦截跨域请求。全局 CORS 配置写在网关这一层最合适不用每个服务各自配一遍。spring: cloud: gateway: globalcors: cors-configurations: [/**]: allowedOriginPatterns: * allowedMethods: - GET - POST - PUT - DELETE allowedHeaders: *注意allowedOriginPatterns是较新版本里的写法替代了已经过时的allowedOrigins。使用allowedOrigins: *时如果请求携带了凭证比如 Cookie会被浏览器拒绝而allowedOriginPatterns可以兼容带凭证的跨域请求。4. 服务间调用与熔断降级从药品查询到库存扣减的完整链路4.1 OpenFeign 声明式远程调用写接口比写 HTTP 客户端省事服务拆开之后一个请求往往要跨多个服务。查药品列表时前端调网关网关转发到 medicine-service但列表里要显示库存数量库存数据在 stock-service 里这就产生了服务间调用。OpenFeign 的用法是定义一个接口用注解声明要调用的服务名和路径Spring 在运行时生成代理对象帮你完成 HTTP 请求的组装和响应解析。FeignClient(name stock-service, fallback StockClientFallback.class) public interface StockClient { GetMapping(/stock/medicine/{medicineId}) StockDTO getStockByMedicineId(PathVariable(medicineId) Long medicineId); }FeignClient的name属性对应注册中心里的服务名不是 IP 也不是域名。调用时 Feign 会从 Eureka 拉取 stock-service 的实例列表结合 Ribbon 做负载均衡。fallback指向一个降级类当远程调用超时或异常时Feign 会执行降级类的逻辑返回一个兜底值而不是直接把异常抛给上层。Component public class StockClientFallback implements StockClient { Override public StockDTO getStockByMedicineId(Long medicineId) { StockDTO dto new StockDTO(); dto.setMedicineId(medicineId); dto.setStockCount(0); dto.setAvailable(false); return dto; } }降级类返回的库存数量是 0同时把available置为 false前端拿到这个标记后可以显示“库存未知”而不是展示错误的库存数字。药品查询这个场景降级成 0 可能误导用户以为断货所以用available字段把状态区分开逻辑上更严谨。Feign 调用超时时间需要单独配置默认的连接超时是 10 秒对于内部服务调用来说太长了。一般会把连接超时设成 2 秒读超时设成 5 秒具体要看接口的耗时。库存查询是简单查询1 秒内应该返回超过 3 秒基本就是服务有问题不如快速失败走降级。ribbon: ReadTimeout: 5000 ConnectTimeout: 2000 MaxAutoRetries: 1 MaxAutoRetriesNextServer: 1这里的重试配置要特别注意。MaxAutoRetries是同一台实例上的重试次数MaxAutoRetriesNextServer是换一台实例重试的次数。对于写入接口比如库存扣减重试可能导致重复提交需要在业务侧做幂等处理。这套系统里如果用 Feign 调采购入库接口建议把重试次数设为 0宁可失败让上层降级也不要因为重试造成库存记录重复。4.2 Hystrix 熔断配置与降级策略适配业务场景熔断器的逻辑是“连续失败达到阈值就打开请求直接走降级不发起真实调用”。这比单纯的超时控制更智能因为服务已经明显异常时继续发请求只是加重负担。Hystrix 在 Spring Cloud 早期版本是标配新版本虽然进入了维护模式但毕业设计源码里出现它的概率很高理解它的配置思路对阅读源码仍然有用。hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 8000 circuitBreaker: requestVolumeThreshold: 20 errorThresholdPercentage: 50 sleepWindowInMilliseconds: 5000requestVolumeThreshold是 10 秒窗口内的最小请求数低于这个数不触发熔断判断。errorThresholdPercentage是错误百分比阈值超过 50% 就打开熔断器。sleepWindowInMilliseconds是熔断打开后多久进入半开状态放少量请求去试探服务是否恢复。这三个参数要配合业务量来调开发环境请求量小阈值设低了容易误熔断生产环境请求量大阈值设高了起不到保护作用。药品管理系统的典型场景里熔断主要用在库存扣减和销售出库这两个写操作上。销售出库时sale-service 先通过 Feign 调 stock-service 扣减库存如果 stock-service 出现慢查询或者死锁Hystrix 熔断后直接返回失败前端提示“系统繁忙请稍后重试”比长时间转菊花要好得多。4.3 库存扣减的事务边界为什么不能在 Feign 调用里开事务库存扣减涉及两个服务的数据变更sale-service 生成销售记录stock-service 扣减库存。如果直接在 sale-service 的Transactional方法里调 Feign 接口一旦库存扣减成功但销售记录插入失败事务回滚只能回滚 sale-service 本地数据库stock-service 那边的扣减已经提交了两边数据不一致。常见做法是把库存扣减做成独立的事务并在业务层做补偿。sale-service 先本地插入销售单状态为“待扣减”然后调 stock-service 扣减库存扣减成功再把状态改为“已完成”失败则记录失败原因通过定时任务重试或者人工介入。这个方案不是分布式事务但在这个业务场景下足够可靠。Transactional public void createSaleOrder(SaleOrderRequest request) { SaleOrder order createOrder(request, OrderStatus.PENDING); saleOrderMapper.insert(order); try { stockClient.deductStock(request.getMedicineId(), request.getQuantity()); order.setStatus(OrderStatus.COMPLETED); saleOrderMapper.updateStatus(order); } catch (Exception e) { order.setStatus(OrderStatus.FAILED); order.setFailReason(e.getMessage()); saleOrderMapper.updateStatus(order); } }注意Transactional只包裹了本地数据库操作Feign 调用在事务里但不是事务的一部分。这里的关键是 catch 住异常之后把订单状态标记为 FAILED而不是直接抛出让事务回滚。回滚的话销售记录没了但 Feign 请求可能已经发出去了库存可能已经扣了反而更难恢复。保留失败记录后续可以通过定时任务查询 FAILED 状态的订单重新执行扣减或者做退款补偿。5. 部署与排错实践Eureka 掉线、配置拉取失败、跨域失效怎么定位5.1 本地一键启动与 Docker 容器化部署多个服务手动逐个启动很痛苦先在根目录的 pom.xml 下执行整体编译再按依赖顺序启动。更推荐的做法是写一个启动脚本把服务按顺序拉起来每个服务启动后检查端口是否监听。#!/bin/bash start_service() { echo Starting $1 ... nohup java -jar $2 logs/$1.log 21 sleep 15 if curl -s http://localhost:$3/actuator/health | grep -q UP; then echo $1 started successfully else echo $1 failed, check logs/$1.log fi } start_service eureka-server ./eureka-server/target/eureka-server.jar 8761 start_service config-server ./config-server/target/config-server.jar 8888 start_service api-gateway ./api-gateway/target/api-gateway.jar 8080脚本里最重要的逻辑是健康检查。直接用 sleep 固定等待时间不可靠机器性能不同服务启动耗时差异很大。通过 actuator 的健康检查接口确认服务真正就绪后再启动下一个避免出现服务启动顺序错乱导致的注册失败。Docker 化部署时服务之间的通信要特别注意。Eureka Server 容器里注册的地址默认是容器 IP业务服务容器访问这个 IP 可能不通。常见做法是给 Eureka Server 显式指定eureka.instance.hostname并且在 docker-compose 网络里用服务名互相访问。eureka: instance: hostname: eureka-server prefer-ip-address: false client: service-url: defaultZone: http://eureka-server:8761/eureka/prefer-ip-address设成 false 后服务会以 hostname 形式注册容器内通过 docker-compose 的服务名进行 DNS 解析。如果这个配置不调Eureka 管理页面里显示的是容器 IP其他服务注册时拿到的是不可达地址调用会一直超时。5.2 高频故障排查场景对照这套源码跑起来之后有几类问题出现频率很高。Eureka 界面能看到服务但调用时报 connection refused优先检查服务实例的注册 IP。如果服务注册的是192.168.x.x或者172.17.x.x这种内网地址而调用方和它不在同一个网络需要微调eureka.instance.ip-address或prefer-ip-address。Windows 上部署尤其明显本机防火墙经常拦截服务间的 HTTP 请求日志里看到ConnectException: Connection refused先确认 8761、8888、8080 这几个端口的入站规则是不是放行了。配置中心拉取失败也是高频问题。Config Server 报 404 时先确认请求的配置文件名是否匹配。spring.cloud.config 拉取规则是{application}-{profile}.yml如果 bootstrap.yml 里spring.application.name是 stock-serviceprofile 是 dev那么 config-server 的配置目录下必须存在stock-service-dev.yml文件。文件名不匹配是最常见的低级错误报错信息却指向 404不太容易想到。跨域配置失效的问题先确认网关版本。Spring Cloud Gateway 的 CORS 配置在较新版本中必须写在spring.cloud.gateway.globalcors下写在spring.web.cors下是不生效的。如果前端请求的路径经过网关后又转发到业务服务业务服务自己别配跨域两层的跨域配置会叠加出现奇怪的响应头冲突。5.3 调用链追踪与日志关联用 Sleuth 和 Zipkin 定位慢接口服务拆开之后一次请求会贯穿三四个服务日志分散在各个服务里排查问题需要把同一请求的日志串起来。Spring Cloud Sleuth 提供 traceId 和 spanId 的注入日志里加上 traceId 后就能把一次调用的全部日志捞出来。Zipkin 则把调用链可视化了每个接口在各服务上的耗时一目了然。spring: zipkin: base-url: http://localhost:9411 sleuth: sampler: probability: 1.0probability: 1.0表示 100% 采样。生产环境一般降到 0.1 就够因为全量采样会带来不小的性能开销。开发阶段为了排查问题全量采样反而方便可以确保每次请求都能在 Zipkin 里找到完整链路。打开 Zipkin 界面按服务名和耗时排序很快就能找到哪个服务拖慢了整个链路。药品管理系统里常见的慢点有两个一个是库存服务里对库存流水表的大范围扫描另一个是查询列表时逐个药品调 Feign 查库存也就是典型的 N1 调用问题。前者的解法是给流水表加时间索引后者要把单个查询改成 Feign 批量查询接口一次传多个药品 ID 返回库存集合把网络开销降下来。Feign 接口设计时就要考虑批量场景而不是单纯做单查。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门