Knife4j 文档聚合:微服务里那些「不请自来」的 API 分组

隔壁服务的 API,怎么跑我这儿来了? 某天启动 auth 服务,打开 http://localhost:8021/doc.html ,想看一眼自己刚调好的三个 API 分组——等等,下拉框里怎么还有 message、order 的分组?点过去全是 404,auth 服务上根本就没有这些接口。 flowchart LR subgraph USER["👤 开发者"] A(["打开 auth 的\ndoc.html"]) end subgraph ACTUAL["期望"] B([只显示自己\n3 个分组]) end subgraph REAL["现实"] C([显示了 8 个\n不同服务的分组]) end A --> B A --> C C --> D{other groups\ntap 404} D -->|yes| E[「这就很烦了」] 代码是同一套代码,springdoc 和 knife4j 版本都是统一管理的,为什么 auth 的文档页面里会出现其他服务的痕迹?某开发者决定,今天不修好不下班。 第一反应:去 knife4j 找配置 这种"在一个服务里看到另一个服务的 API",第一感觉就是 knife4j 的网关聚合功能 在作祟。毕竟 knife4j 有个专门的 knife4j-aggregation-spring-boot-starter ,专门用来在 gateway 上聚合所有微服务的文档。 二话不说,给每个服务的 application.yml 加上: knife4j: enableAggregation: false 重启,刷新——没变化,其他分组稳如泰山地挂在下拉框里。 ...

三月 14, 2023 · 3 分钟 · 607 字 · yaomingye

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 Bean 生命周期与懒加载:从一次 Starter 踩坑说起

Spring Bean 生命周期与懒加载:从一次 Starter 踩坑说起 起因:一个 @PostConstruct 引发的血案 封装了一个 Redis 工具类的 Spring Boot Starter,里面有个组件叫 WorkIdAllocator ,它在 @PostConstruct 中做了这么一件事: @PostConstruct public void init() { setNextSnowFlaskWorkerId(); // 连接 Redis,分配一个雪花算法 WorkerId } 看起来没毛病——服务启动时自动分配到 WorkerId。但问题来了:Redis 的连接参数( spring.data.redis.host )放在 Nacos 配置中心,通过 shared-configs 加载。而 @PostConstruct 在 Bean 属性注入完成后就立刻执行,那时候 Redis 配置还没加载到 Spring 的 Environment 中。 结果: host = null → 默认 localhost:6379 → 连不上 → 启动失败。 修复方式也很简单——加个 @Lazy : @Lazy @Component public class WorkIdAllocator { // 第一次被调用时才执行 @PostConstruct } 问题虽然解决了,但某个开发者好奇心被勾起来了:Spring Bean 的生命周期到底分几个阶段?扩展点在什么时候执行?@Lazy 到底干了什么? ...

三月 8, 2023 · 6 分钟 · 1277 字 · yaomingye

单体拆分微服务:9 个服务踩出来的 7 个典型错误

单体拆微服务,这 7 个坑你踩过几个? 接手了一套从单体架构拆分为微服务的 Spring Cloud Alibaba 项目。9 个服务,Spring Boot 3.3.5,集齐了 Nacos、Gateway、Sentinel、RocketMQ、ShardingSphere、Elasticsearch 全家桶——看 POM 文件像一份微服务教科书。 实际跑起来就发现问题了:项目虽然拆成了 9 个模块,但在架构思维上仍然是个单体。 用了微服务的壳,没改掉单体时代的坏习惯。一顿排查下来,发现了 7 个典型错误。 错误一:全量包扫描——每个服务都在扫整个宇宙 第一个映入眼帘的就是各个 Application 类上的注解: @ComponentScan(basePackages = "cn.net.mall") 9 个服务里有好几个直接扫整个项目包树。这意味着什么? mall-pay 启动的时候,Spring 会去扫描 mall-common 下的所有类,包括 cn.net.mall.util.RedisUtil 。而 RedisUtil 又依赖 StringRedisTemplate ,这个类来自 spring-boot-starter-data-redis ,偏偏 mall-pay 的 POM 里没加这个依赖。 mall-pay 启动 → 扫描 cn.net.mall → 发现 RedisUtil → 尝试创建 → StringRedisTemplate 不在 classpath → ClassNotFoundException → 启动失败 flowchart LR subgraph SCAN["全量扫描 `cn.net.mall `"] PAY["mall-pay\n@ComponentScan"] COMMON["mall-common\nRedisUtil ← 依赖 → StringRedisTemplate"] end subgraph CLASSPATH["pay 的 classpath"] DEPS["mall-common.jar\n(但有 optional=true)"] MISSING["❌ StringRedisTemplate 不在"] end PAY -->|"扫描到"| COMMON COMMON -.->|"尝试创建 bean"| MISSING MISSING -->|"ClassNotFoundException"| CRASH["启动崩溃"] classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:2px,color:#e5e7eb; classDef reject fill:#450a0a,stroke:#dc2626,stroke-width:2px,color:#fecaca; classDef highlight fill:#431407,stroke:#ea580c,stroke-width:2px,color:#fed7aa; class PAY,COMMON process; class MISSING,CRASH reject; class DEPS highlight; 更隐蔽的问题是:全量扫描让分仓库成为泡影。 微服务的核心理念之一就是独立开发、独立部署。如果每个服务都假设"所有模块在同一个 classpath 上",那一旦把服务拆到独立 Git 仓库,全量扫描就会漏掉其他服务的类——因为它根本不在 classpath 上。 ...

三月 6, 2023 · 8 分钟 · 1559 字 · yaomingye

Sentinel Dashboard 容器化部署踩坑全记录:从端口映射到认证配置的血泪史

被一个社区镜像折磨的 24 小时 目标说明 这篇博客的目标很朴素:让读者用 10 分钟把 Sentinel Dashboard 跑起来,而不是花一整天跟一个社区镜像死磕。 某开发者在搭建微服务治理平台时,需要部署 Sentinel Dashboard 作为流量治理控制台。本以为 docker run 一把梭,结果被 bladex/sentinel-dashboard:1.8.6 这个社区镜像折腾了整整一天——端口改不掉、环境变量传了等于没传、配置文件挂载不生效、启动就崩溃、浏览器打开 401……踩了个遍。 本文将完整记录这 7 个坑的根因、排查过程和最终解决方案。所有配置已在 Debian 13 + Docker 26+ 下验证通过。 📌 前置知识:需要了解基本 Docker 操作和 Spring Boot 配置文件概念。 前置条件 项目 要求 操作系统 Linux(本文基于 Debian 13 WSL2) Docker 26+ Docker Compose v2+ 目标端口 9903(按需调整) 验证命令: docker --version # Docker version 26.x.x docker compose version # Docker Compose version v2.x.x 环境搭建 创建一个部署目录,后续所有文件都在此目录下操作: mkdir -p ~/dev-env/sentinel && cd ~/dev-env/sentinel 先简单拉个镜像试试水: ...

三月 4, 2023 · 5 分钟 · 855 字 · yaomingye

Nacos 配置管理实战:config.import 与 bootstrap.yml 的取舍、多环境方案、模板标准化

Nacos 配置管理:一套标准化方案如何搞定 9 个微服务 接手了一套 Spring Cloud Alibaba 微服务项目。读完代码后先看了一遍它的配置管理——毕竟 9 个服务各有各的数据库、Redis、Token 密钥,还不用同一套连接机制,这不配置爆炸谁配置爆炸。 检查结果不出所料:有的服务用 bootstrap.yml 连 Nacos,有的用 config.import ,还有的既没 bootstrap.yml 也没 config.import ,全靠本地 application.yml 硬撑着。更离谱的是,9 个服务在 Nacos 上重复配置了 9 遍 Redis、9 遍 JWT 密钥,改一次密码要改 9 个 dataId。 分享一套标准化方案,从 9 个服务的混乱配置中理出了头绪。 bootstrap.yml 还是 config.import? Spring Cloud Alibaba 项目连接 Nacos 有两种做法。第一种是传统方案,在 bootstrap.yml 中配置 Nacos 参数: `` `yaml bootstrap.yml — 旧方案 spring: cloud: nacos: config: server-addr: ${NACOS_ADDR:localhost:8848} namespace: ${NACOS_NAMESPACE:mall} file-extension: yaml 需要额外引入 `spring-cloud-starter-bootstrap` 依赖才能生效。这是 Spring Cloud 2020 之前的标准做法。 第二种是新方案,直接在 `application.yml` 中通过 `spring.config.import` 指定: `` `yaml # application.yml — 新方案 spring: config: import: nacos:${spring.application.name}.yaml Spring Cloud 2023.x 已默认关闭 bootstrap 上下文,官方推荐使用 config.import 。 ...

三月 2, 2023 · 5 分钟 · 1030 字 · yaomingye

接手微服务项目的踩坑与重构:鉴权链路、补偿机制与 API 文档整理

接手了一套微服务项目,代码看起来挺像回事,仔细一看全是坑 这套项目表面上看骨架搭得不错——Spring Cloud Alibaba 全家桶、17 个 Maven 模块、9 个微服务、分库分表、ES 双写、SkyWalking 链路追踪,基础中间件能怼的全都怼上去了。 但真的跑起来、改起来、审计起来,才发现业务逻辑有不少问题:鉴权链路缺半截、下单不扣库存、@NoLogin 散落几十个地方、JWT 只存了 username 导致全链路 Redis 查重。基础设施搭得好,不等于项目能经受住实际业务场景的考验。 本文将项目里几个典型问题摆出来,聊聊踩坑过程和修复思路,同时配图说明关键流程的改造前后对比。 JWT claims:只放 username,剩下的全塞 Redis 第一个让人困惑的设计点在于 JWT 的使用方式。看下 Token 生成代码: // UserTokenHelper.java — 原实现 public String generateToken(String username, String json) { String token = Jwts.builder() .setSubject(username) // ← 只放了 username .setExpiration(generateExpired()) .signWith(SignatureAlgorithm.HS512, tokenSecret) .compact(); redisUtil.set(getTokenKey(username), token, 3600); // Redis 存一份 redisUtil.set(getUserKey(username), json, 3600); // 用户完整信息也存 Redis return token; } JWT 的 claims 字段本质上设计来承载结构化信息,签名保证不被篡改。但这里把它当成了一个随机字符串来用——JWT 里只放了 sub: "admin",userId、角色、权限全部丢进 Redis。 ...

二月 28, 2023 · 4 分钟 · 849 字 · yaomingye

Live2D 模型渲染崩溃:游戏提取动作文件的 TotalPointCount 元数据错误排查与修复

Live2D 调包侠踩坑记:一个「少算 44 个点」引发的渲染崩溃 故事的开端 某开发者最近在折腾网页 Live2D 看板娘。项目基于 naihe-live2d-widget-v3,它对标的是经典的 live2d-widget,但底层换成了最新的 Cubism SDK for Web v5,专门渲染 .moc3 格式的新版模型。 一切看起来都很顺利——直到把从游戏里提取的一个猫耳角色模型(编号 416)丢进去。 模型加载了,但动作一播放就崩。刷新,又崩。时好时坏,但大部分时间页面一片空白,控制台躺着一行红字: Uncaught (in promise) TypeError: Cannot set properties of undefined (setting 'time') 对调包侠来说,这可能就是那一刻想关电脑的信号。 定位问题:不是模型坏了,是 Meta 骗了 SDK 报错指向 live2d-sdk.js 中 parse 函数的某个 .time 赋值操作。顺着调用栈往上翻: parse → create → loadMotion → preLoadMotionGroup → setupModel → loadAssets ——动作文件解析时崩溃了。 📌 前置知识:Live2D 的 .motion3.json 文件描述了一条条动画曲线。每条曲线包含一串「段」(segments),每个段由若干个「控制点」(points)定义。Meta 段会预先声明总点数和总段数,方便 SDK 预分配内存。 某个开发者的直觉是:会不会是 Meta 里声明的数量跟实际数据对不上? 写了一段简单的验证脚本跑了一下: let calculatedPoints = 0; for (const curve of curves) { const segs = curve.Segments; let pos = 0, first = true; while (pos < segs.length) { if (first) { calculatedPoints++; pos += 2; first = false; } const segType = segs[pos]; switch (segType) { case 0: calculatedPoints++; pos += 3; break; // 线性 case 1: calculatedPoints += 3; pos += 7; break; // 贝塞尔 case 2: calculatedPoints++; pos += 3; break; // 步进 case 3: calculatedPoints++; pos += 3; break; // 反向步进 } } } 结果一看——全都对不上。 ...

二月 26, 2023 · 3 分钟 · 614 字 · yaomingye

B/S架构常见网络攻击与SpringBoot/Cloud防御实践——XSS、CSRF、SQL注入、DDoS与JWT安全的攻防图谱

B/S攻防:五类攻击与Spring体系的应对 📌 前置知识:本文假设读者用过 Spring Boot、知道 Cookie/Session/Token 的基本概念。Spring Security 的 Filter Chain 不熟没关系——每段防御代码会说明它在过滤器链中的位置。 XSS:当用户输入变成了可执行脚本 跨站脚本攻击(Cross-Site Scripting)的本质是:攻击者把 JavaScript 塞进用户输入,服务器原样输出到 HTML,浏览器执行了这段恶意脚本。 sequenceDiagram participant Attacker as 攻击者 participant Victim as 受害者浏览器 participant Server as 有漏洞的服务器 participant DB as 数据库 Attacker->>Server: "POST /comment 提交评论\n内容: script stealCookie()" Server->>DB: 存入评论(未做转义) Victim->>Server: GET /article?id=123 Server->>DB: 查询评论列表 DB-->>Server: 返回含脚本的评论 Server-->>Victim: "script stealCookie()\n浏览器解析并执行" Victim->>Attacker: "恶意脚本执行\nCookie 被发送到攻击者服务器" Spring Boot 的防御分三层: ① 输出转义——Thymeleaf 默认做 // Thymeleaf 模板中默认对变量做 HTML 转义 // <div th:text="${comment.content}"> → < 变成 &lt;,脚本失效 // 如果你用 JSP 或手动拼 HTML,务必用 escapeHtml: String safe = HtmlUtils.htmlEscape(userInput); ② 输入过滤——Spring 全局拦截 ...

二月 24, 2023 · 5 分钟 · 927 字 · yaomingye
Cat Radio