Spring Cloud 微服务接入 BFF 聚合层:从一个混乱的项目重构说起

当你的单体项目被拆成微服务,前端第一个崩溃 某天接手了一个从老单体拆出来的微服务电商项目。技术栈倒是很"大厂"——Spring Cloud Alibaba、Nacos、Sentinel、RocketMQ、ShardingSphere,你能想到的全塞上了。 但前端同事过来敲门的时候,事情就不太对劲了。 “咱这项目一共几个文档地址?” “9 个。"(每个后端服务一个 Knife4j 页面) “那我要调一个登录接口,该看哪个服务的文档?” “……好问题。” 这就是典型的微服务拆了,但没完全拆——后端确实拆成了 9 个独立服务,可前端仍然需要知道每个服务的地址、每个接口的路径、每个返回的字段含义。而且很多接口其实需要前端自己拼数据:登录完了再查一遍用户信息、再查一遍菜单权限、再查一遍角色列表。 前端不是在写业务,是在做 API 聚合。 BFF:不是新概念,但能解决真问题 BFF(Backend For Frontend)的核心思路很简单:每个前端都有一个专属的后端入口,这个入口干三件事: 聚合 — 把多个后端服务的数据合并成前端需要的一站式响应 裁剪 — 只返回前端真正需要的字段,不裸奔整个数据库实体 隔离 — 后端再怎么拆、再怎么重构,前端代码不用动 架构上看起来就是中间多了一层: flowchart LR subgraph CLIENT["📱 前端"] WEB(["管理后台 Web"]) APP(["移动端 小程序"]) end subgraph GW["🚪 网关层"] GATEWAY[Spring Cloud Gateway\nJWT · CORS · Sentinel] end subgraph BFF["🎯 BFF 聚合层"] ADMIN_BFF["mall-admin-api\n管理后台 BFF\n端口 8090"] MOBILE_BFF["mall-mobile-api\n移动端 BFF\n端口 8091"] end subgraph BACKEND["⚙️ 业务微服务"] AUTH[mall-auth-api] BASIC[mall-basic-api] PRODUCT[mall-product-api] ORDER[mall-order-api] MARKETING[mall-marketing-api] OTHERS[其余 4 个服务...] end WEB -->|"/api/admin/**"| GATEWAY APP -->|"/api/mobile/**"| GATEWAY GATEWAY --> ADMIN_BFF GATEWAY --> MOBILE_BFF ADMIN_BFF -->|Feign 调用| BACKEND MOBILE_BFF -->|Feign 调用| BACKEND classDef bffFill fill:#2e1065,stroke:#a855f7,stroke-width:2.5px,color:#f8fafc,font-weight:bold; class ADMIN_BFF,MOBILE_BFF bffFill; 为什么要加这一层?直接转发不行吗? 接手时项目就已经有两个"BFF 模块"了—— mall-mobile-api 和 mall-admin-api 。但打开一看,里面就一个 ForwardController ,用 RestTemplate + LoadBalancerClient 把所有请求原封不动转发到后端服务: ...

三月 12, 2023 · 4 分钟 · 739 字 · yaomingye

GitHub Packages 发布 Maven 库:从 Token 配置到 BOM 管理

GitHub Packages 发布 Maven 库:从 Token 配置到 BOM 管理 目标 把一个多模块 Maven 项目发布到 GitHub Packages,并让其他项目通过 BOM 统一引用。全程使用 GitHub 免费额度,不搭私有 Nexus。 前置条件 条件 说明 GitHub 账号 一个,免费套餐即可 Personal Access Token write:packages + read:packages 权限 Maven 3.6+ 构建工具 Java 17+ 运行时 环境搭建 第 1 步:生成 GitHub Token GitHub 右上角头像 → Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token 勾选权限范围: ✅ write:packages (发布包) ✅ read:packages (下载包) ✅ repo (访问仓库,write:packages 自动依赖) Token 创建后只会显示一次,复制保存好。有效期的建议:如果用于本地开发,设 30 ~ 90 天;如果用于 CI/CD,设成永不过期并定期轮换。 ...

三月 10, 2023 · 3 分钟 · 553 字 · yaomingye

四种限流算法在 Spring Cloud Gateway 中的实现:固定窗口、滑动窗口、漏桶、令牌桶

在 Gateway 里手写四种限流算法 目标说明 网关是流量的咽喉,限流是网关最重要的能力之一。这篇文章的目标很明确: 理解四种主流限流算法的核心逻辑:固定窗口、滑动窗口、漏桶、令牌桶 每种算法都能写出来并跑通,不只是看概念 集成到 Spring Cloud Gateway 中,作为自定义 GatewayFilter 使用 了解生产级方案:Redis + Lua 分布式限流 读完这篇文章,读者应该能回答:“为什么 Sentinel 选滑动窗口、Gateway 选令牌桶?“以及"如果让你自己写一个限流过滤器,你怎么写?” 前置条件 开始之前,确保环境满足以下条件: 依赖 版本要求 用途 JDK 11+ 运行 Spring Boot 应用 Spring Boot 2.7.x 基础框架 Spring Cloud 2021.0.x Gateway 依赖 Spring Cloud Gateway 3.1.x 网关核心 Redis(可选) 6.0+ 分布式限流 JMeter(可选) 5.5+ 压测验证 验证命令: java -version # 应输出 11 或更高 mvn -version # 确认 Maven 可用 redis-cli ping # 如果做分布式限流,确认 Redis 连通 ⚠️ 新手提示:本文的代码可以在一个独立的 Spring Boot 项目中运行,不需要完整的微服务集群。只要一个 Gateway 项目 + 一个后端服务即可验证。 ...

二月 12, 2023 · 8 分钟 · 1658 字 · yaomingye

SpringCloud微服务测试实战

SpringCloud微服务测试实战:分层策略、完整代码与AI时代的新思路 问题切入 写了一万行业务代码,测试用例只有三行——这种事情在微服务项目里尤其常见。不是开发者不想写测试,而是SpringCloud环境下的测试确实比单体应用复杂得多:服务之间通过Feign/Dubbo调用、配置在Nacos远端、消息通过RocketMQ/Kafka传递、数据库还分库分表。随便写个Service都依赖五六个外部组件,怎么测? 先说结论:微服务测试的核心思路是分层隔离。不同层级关注不同的验证目标,用不同的策略来隔离外部依赖。每一层有明确的边界和颗粒度,而不是不管三七二十一全部启动Spring容器。 flowchart TD subgraph Top[🔺 测试金字塔:越往上越慢、越贵、越少] subgraph L5[⏱️ 端到端测试] E2E[🌐 E2E测试\n全链路验证\n数量:极少] end subgraph L4[🔗 契约/集成测试] CONTRACT[📋 契约测试\nFeign/Dubbo接口契约\n数量:少量] INTEG[🔧 Service集成测试\nSpring容器+真实DB/Redis\n数量:适中] end subgraph L3[🧩 切片测试] WEB[🌐 Web层测试\n@WebMvcTest\n仅Controller上下文] DATA[🗄️ 数据层测试\n@DataJpaTest\n仅JPA上下文] end subgraph L2[⚡ 单元测试] UNIT[📐 纯单元测试\n无Spring容器\nMock所有依赖\n数量:大量] end end L5 --> L4 L4 --> L3 L3 --> L2 classDef layer fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#e5e7eb; classDef highlight fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; classDef data fill:#052e16,stroke:#16a34a,stroke-width:1.5px,color:#bbf7d0,font-weight:bold; class E2E,CONTRACT,INTEG,WEB,DATA,UNIT layer class L2 highlight class L2 data 这个金字塔翻译成SpringCloud语境下的操作指南,就是下面这张分层策略表: 测试层级 启动Spring容器? 真实依赖 Mock/Stub 单个耗时 覆盖目标 纯单元测试 否 无 所有外部依赖 毫秒级 业务逻辑分支 Web层切片 是(仅Controller) 无 Service/Mapper 1 ~ 3秒 参数校验/序列化/异常处理 数据层切片 是(仅JPA) 内嵌数据库(H2) 无 1 ~ 3秒 SQL映射/查询方法 Service集成测试 是(完整) H2/内嵌Redis Feign/MQ/外部API 3 ~ 8秒 事务边界/缓存/业务编排 契约测试 是(Consumer端) 无 对Provider的Stub 2 ~ 5秒 Feign接口签名一致性 端到端测试 是(全部服务) 全部 无 分钟级 全链路连通性 ⚠️ 新手提示:这张表建议存下来当速查卡。每次写完代码准备写测试时,先对着表想清楚"这一层该启动什么、该Mock什么",比盲目写省一半时间。 ...

一月 26, 2023 · 9 分钟 · 1870 字 · yaomingye

从写完代码到上线运行

从写完代码到上线运行:SpringBoot微服务CI/CD完整链路 目标说明 这篇教程要解决一个很实际的问题:写完SpringBoot微服务代码之后,怎么把它弄到线上稳定运行? 很多开发者(尤其是刚入行的)对这块的认知是模糊的——“代码写完了,接下来是不是找个服务器丢上去就行了?” 实际过程远比这个复杂,涉及到测试验证、容器化、CI/CD流水线、配置中心、网关路由等一系列环节。 本教程将以一个典型的SpringBoot微服务项目为例,从代码提交前的本地测试开始,一步步走到Kubernetes集群上的生产环境部署。每个环节都给出完整可复制的脚本和配置文件,不跳步,不给半截代码。 ⚠️ 新手提示:这篇教程假设读者能独立用SpringBoot写CRUD接口,但对DevOps/运维侧的流程不熟悉。如果连SpringBoot项目怎么创建都还不太清楚,建议先去翻翻SpringBoot入门文档再回来看。 前置条件 开始之前,先确认本地环境是否满足以下条件。每项后面附了验证命令,直接在终端里跑一下就能确认。 序号 前置条件 最低版本 验证命令 说明 1 JDK 8+ java -version 编译和运行SpringBoot项目 2 Maven 3.6+ mvn -version 项目构建和依赖管理 3 Docker 20.10+ docker version 容器镜像构建 4 Git 2.30+ git version 版本控制和协作 5 SpringBoot项目 2.x mvn spring-boot:run 已有可正常启动的项目 6 kubectl 1.20+ kubectl version 部署阶段需要(可最后装) 📌 前置知识:Docker的基础概念(镜像、容器、仓库三者的关系)。如果不清楚,可以先跑一遍 docker run hello-world 感受一下,然后大致了解 docker build、docker push、docker pull 三条命令的作用。 环境搭建 开始实践之前,先把必要的环境准备到位。下面按依赖顺序逐步完成。 确认Docker环境 # 检查Docker是否安装并运行 docker version # 预期输出(版本号可能不同): # Client: Docker Engine - Community # Version: 20.10.16 # Server: Docker Engine - Community # Engine: # Version: 20.10.16 # 如果Docker daemon没启动,先启动它 # Linux: sudo systemctl start docker # Mac/Windows: 打开Docker Desktop 安装Docker Compose(用于本地集成测试) # 检查是否已安装 docker compose version # 预期输出:Docker Compose version v2.10.2 # 如果没有,参考官方文档安装: # https://docs.docker.com/compose/install/ 配置Maven settings.xml Maven默认从中央仓库拉依赖,在国内网络环境下可能很慢。建议配置国内镜像: ...

一月 25, 2023 · 13 分钟 · 2676 字 · yaomingye

Raft 协议:选举、日志复制与强一致

Raft 协议 本文是分布式算法科普系列第二篇。上一篇讲了 Distro 协议如何用"去中心化 + 异步同步"实现 AP 模型——写完立刻返回、事后慢慢对齐。这一篇讲它的反面:Raft 如何用"选出一个老板 + 事事多数同意"实现 CP 模型——宁可暂时不可用、绝不返回错误数据。 一、故事:Paxos 太难了,于是有了 Raft 在 Raft 出现之前,分布式共识领域有一个"上古神器"——Paxos。Paxos 由 Leslie Lamport(就是写 LaTeX 的那位)在 1989 年提出,理论正确性无可挑剔,但有一个致命的工程问题:几乎没有人能真正看懂它。 Lamport 在 1998 年发表了一篇补充论文《Paxos Made Simple》,摘要第一句话就是——“The Paxos algorithm, when presented in plain English, is very simple."(用大白话讲,Paxos 其实很简单。)但工程界的反馈很统一:不,它一点也不 simple。 这不是段子,是真实历史。Google 的 Chubby 分布式锁系统在实现 Paxos 的过程中遇到了大量问题,Chubby 的作者 Mike Burrows 有一句著名的吐槽:“世界上只有两种共识算法——Paxos 和那些没人能证明正确的算法。” 2013 年,斯坦福大学的博士生 Diego Ongaro 和导师 John Ousterhout 决定正面解决这个问题。他们的出发点和前面所有人都不一样——把"可理解性"作为算法的首要设计目标,而不是附带的副产品。 Ongaro 从头设计了一个全新的共识算法,刻意把整个协议拆成三个相对独立的模块——领导者选举、日志复制、安全保证——每个模块都可以单独理解。2014 年,他们发表了论文《In Search of an Understandable Consensus Algorithm》(寻找一个可理解的共识算法),Raft 正式诞生。 ...

一月 19, 2023 · 4 分钟 · 803 字 · yaomingye

Redis 高可用架构:主从、哨兵、集群与分片

主从、哨兵、集群与分片——高可用架构全解 一、问题切入:单机 Redis 能走多远 开发环境启动一个 Redis 实例,redis-cli 连上去,SET / GET 一切正常。然后某天线上出了问题: 促销活动期间,Redis 内存飙到 32GB 上限,新的写入被拒绝 服务器宕机,缓存全丢,所有请求直接穿透到 MySQL,服务雪崩 同一个 Key 被几百个并发请求同时修改,客户端频繁收到 READONLY 错误 这些问题指向同一个根因:单机 Redis 有三个硬伤。 硬伤 表现 后果 内存上限 一台机器最多几百 GB 内存,存不下全量数据 频繁淘汰 / OOM 单点故障 进程挂掉 → 整个缓存层不可用 请求穿透到 DB,服务雪崩 写吞吐瓶颈 单机只能处理几万 QPS 的写入,核心在主线程串行执行 促销期间扛不住 Redis 为了解决这三个问题,依次演进出了三种架构模式——主从复制、哨兵模式、集群模式。理解这三者之间的关系,是开发者对接云 Redis 服务的前提。 flowchart TD classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2px,color:#fce7f3,font-weight:bold; classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#e5e7eb; classDef highlight fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; S[📦 单机 Redis] --> M[📋 主从复制] M --> S2[🔍 哨兵模式] S2 --> C[🔗 集群模式] M --> M1["解决问题: 读扩展 + 数据冗余\n新增问题: 手动故障转移"] S2 --> S2A["解决问题: 自动故障转移\n新增问题: 写仍单点"] C --> C1["解决问题: 写扩展 + 海量数据\n新增问题: 跨槽限制"] class S,M,S2,C process; class M1,S2A,C1 highlight; class S startEnd; 这张演进路线图的含义:后一层架构不是替代前一层,而是叠加。集群模式内置了主从复制和哨兵的部分能力,但三者解决的问题域并不完全相同。下面逐一展开。 ...

一月 17, 2023 · 6 分钟 · 1251 字 · yaomingye

时序数据库在物联网日志系统中的设计与实践

时序数据库在物联网日志系统中的设计与实践:从数据结构到SpringBoot接入 一、一张表撑不住了 📌 前置知识:了解关系型数据库(RDBMS)基本概念,知道什么是SQL、索引、事务。知道什么是磁盘IO,理解顺序IO和随机IO的大致性能差距(约一个数量级)。 某开发者接手了一个物联网项目,设备每秒上报一次数据,1000台设备跑了一周,MySQL 的 iot_logs 表已经几千万行,查询一条设备最近一小时的数据要跑十几秒。加了索引,写入又慢得让人抓狂。这种场景,写过的都懂——关系型数据库不是不能存时序数据,而是它的每一个设计决策都和时序场景的需求背道而驰。 物联网日志(IoT Logs)具有几个典型特征: 写入密集且顺序:数据按时间顺序持续产生,极少更新或删除 查询模式固定:通常按时间范围 + 设备ID聚合查询,很少跨设备做复杂JOIN 数据冷热分明:最近几小时的数据被频繁访问,一周前的数据偶尔查一次 压缩空间巨大:传感器数据变化缓慢,相邻时间点数据高度相似,而 RDBMS 的通用压缩算法根本不认识这种模式 这就引出了一个核心问题:时序数据库(TSDB,Time Series Database)到底在数据结构层面做了哪些改造,让它和经典关系型数据库(RDBMS)产生了本质差异?以及如何在 Spring Boot 项目中把这些 TSDB 用起来? 二、数据结构层面的根本差异 2.1 行式存储 vs 列式存储 RDBMS 以 行(Row) 为单位组织数据,一行数据的各个字段在磁盘上连续存放。这种设计让单行读写非常高效——适合 OLTP 场景下"查一行、改一行"的套路。但面对时序查询时,问题就暴露了:查询"过去一小时内所有传感器的温度平均值",只需要温度这一个列,行式存储却会把湿度、气压、设备状态等几十个列一并从磁盘读进内存——IO 利用率奇低。 TSDB 采用 列式存储(Columnar Storage),将同一列的数据在磁盘上连续存放。查询温度列时,只读取温度相关的数据块,其他列完全不参与 IO。这和 ClickHouse 等 OLAP 引擎的思路一致,但 TSDB 在列式基础上又叠加了时间维度的特殊优化。 flowchart TD subgraph row["📦 行式存储 RDBMS"] direction TB r1["Row1│ts:1000│temp:25.3│hum:68│loc:WH1"] --> r2["Row2│ts:1001│temp:25.4│hum:67│loc:WH1"] r2 --> r3["Row3│ts:1002│temp:25.3│hum:68│loc:WH1"] end subgraph col["📦 列式存储 TSDB"] direction TB c_ts["Col_ts: 1000, 1001, 1002"] --> c_temp["Col_temp: 25.3, 25.4, 25.3"] c_temp --> c_hum["Col_hum: 68, 67, 68"] c_hum --> c_loc["Col_loc: WH1, WH1, WH1"] end row -.-> col classDef default fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#e5e7eb; classDef highlight fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; class r1,r2,r3,c_ts,c_temp,c_hum,c_loc default class r1,c_temp highlight ⚠️ 新手提示:不要混淆"列式存储的数据库"和"时序数据库"。列式存储是 TSDB 的技术手段之一,但不是全部。ClickHouse 是列式存储的 OLAP 数据库,但它不是 TSDB——它没有时间维度的特殊处理(如自动分区、自动过期删除)。 ...

一月 16, 2023 · 7 分钟 · 1406 字 · yaomingye

七牛云 OSS + SMTP 邮件:两个轻量级第三方接入实战

七牛云 OSS + SMTP 邮件接入 第1步:目标说明 — 图片上传和发邮件,每个项目的标配 电商项目两个常见的外部依赖:图片存储和邮件通知。商品图、用户头像得有个地方存,登录异常告警、注册欢迎得有个通道发。 Mall 项目用七牛云 OSS 存图片和文件(CDN 加速),用 SMTP 发邮件(Freemarker 模板渲染 HTML 正文),两个接入都不复杂,加起来不超过 200 行代码。本教程从申请凭证到代码封装,两套接入一次讲完。 第2步:前置条件 条件 七牛云 OSS SMTP 邮件 账号 qiniu.com 注册,实名认证 任意邮箱服务(163、QQ邮箱、企业邮箱) 凭证 AccessKey / SecretKey 邮箱地址 + SMTP 授权码 存储空间 在七牛云控制台创建 Bucket 无需 域名 Bucket 绑定 CDN 加速域名 无需 ⚠️ 新手提示:七牛云 OSS 和阿里云 OSS 是竞品,功能几乎一样。Mall 项目选了七牛云,如果公司已经在用阿里云 OSS,代码结构完全能用,换 SDK 即可。接入模式是通用的。 第3步:环境搭建 — 七牛云 OSS 添加 Maven 依赖 <dependency> <groupId>com.qiniu</groupId> <artifactId>qiniu-java-sdk</artifactId> <version>7.4.0</version> </dependency> 配置属性类 @Component @ConfigurationProperties(prefix = "oss.qiniu") @Data public class QiNiuConfig { private String accessKey; // 七牛云 AK private String secretKey; // 七牛云 SK private String bucketPictureName; // 图片 Bucket 名称 private String domainPicture; // 图片 CDN 域名 private String bucketFileName; // 文件 Bucket 名称 private String domainFile; // 文件 CDN 域名 } 图片和文件分两个 Bucket——图片需要 CDN 加速 + 图片处理(裁剪、加水印),文件的访问频率低但需要支持大文件下载,分开管理方便设置不同的生命周期策略。 ...

一月 15, 2023 · 4 分钟 · 846 字 · yaomingye

支付宝支付接入:沙箱调试 + EasySDK + QR 码支付实战

支付宝支付接入 第1步:目标说明 — 支付接入最怕的是什么 不是代码复杂,而是"没法在本地调"——每次测试都要真的扫码付钱,退款、对账、异常场景根本模拟不了。 支付宝提供了沙箱环境(sandbox),完全模拟生产接口的行为,但用的是虚拟账户和虚拟资金。开发人员在沙箱里可以反复测试支付、退款、异常场景,不花一分钱。 Mall 项目对接的是支付宝"当面付"(FaceToFace),生成二维码让用户扫码支付。本教程覆盖:沙箱环境申请 → RSA2 密钥配置 → EasySDK 集成 → QR 码生成 → MockPay 开发模式 → 生产切换。 第2步:前置条件 条件 要求 获取方式 支付宝开放平台账号 已注册并实名 open.alipay.com 沙箱环境 已开通(免费) 开放平台 → 控制台 → 沙箱环境 沙箱应用 自动创建 沙箱环境会自动生成一个测试应用 RSA2 密钥对 2048 位 支付宝密钥生成工具 或 openssl genrsa ⚠️ 新手提示:沙箱环境和正式环境是两套完全独立的系统——沙箱的 APPID、网关地址、密钥、支付宝公钥都和正式环境不同。Sandbox 网关是 openapi-sandbox.dl.alipaydev.com,生产网关是 openapi.alipay.com。切换环境不是改一两个配置项,而是整套凭证都得换。 第3步:环境搭建 添加 Maven 依赖 <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-easysdk</artifactId> <version>2.2.0</version> </dependency> EasySDK 是支付宝官方封装的"开箱即用"SDK。老版 SDK alipay-sdk-java 需要手动构造请求参数、手动验签,代码量是 EasySDK 的 3 ~ 5 倍。EasySDK 一个 Factory.Payment.FaceToFace().preCreate() 就完成预下单。 ...

一月 14, 2023 · 4 分钟 · 655 字 · yaomingye
Cat Radio