今日日报:文档分组、接口聚合、配置扫描

今日日报 干了什么 Swagger 文档三层分组 给 8 个微服务分了三个文档组——前端接口、后台接口、内部微服务接口。加了 emoji 前缀区分,各服务不再相互干扰。所有内部接口统一加了 /v1/internal/ 路径前缀。 BFF 聚合 之前 BFF 层基本是透传,今天把管理后台的控制器补全了。系统管理、商品管理、订单管理、营销管理、基础数据都有了对应的 BFF 控制器和 Swagger 文档。小程序那边原本就做了首页和商品详情的聚合,今天补了用户中心。 前端接口映射 扫了两个前端项目(小程序和管理后台),列了 50 多个 API 调用,逐个去后端代码里 grep 确认接口路径。发现了 4 个前端写了但后端不存在的接口,也确认了一批属于另一个系统的接口。 DTO 注解补齐 一天下来给 103 个 DTO 和 Entity 加了 @Schema 注解,类级和字段级都有,Swagger 文档不再是黑盒了。 Nacos 扫描 拉到了全部 10 个服务的 Nacos 配置,更新了配置文档,补了 BFF 服务的 template 文件。 README 瘦身 把 README 里超长的内容拆到 docs 目录下,加了架构改进建议文档,整理了 28 项待改进问题。 明日计划 继续推进遗留任务。

三月 18, 2023 · 1 分钟 · 64 字 · yaomingye

微服务文档重构:API 分组、BFF 聚合与文档拆迁

今天干了啥:分组、聚合、拆文档 今天没写啥牛逼的业务代码,大部分时间在跟文档和接口结构较劲。记个流水账。 一、发现 doc.html 不对劲 打开一个微服务的 Knife4j 文档页,下拉框里赫然列着七八个其他服务的分组。一点就 404,明摆着是隔壁服务跑这儿串门了。 第一反应是 knife4j 的配置问题,加了一堆 enableXxx: false ,重启——纹丝不动。后来发现 knife4j 基础 starter 压根没有跨服务聚合功能。真正的原因藏在 SpringDoc 的配置链路里,某些共享配置文件往 swagger-config 端点的 urls 字段里塞了外部的分组 URL。 最后在每个服务的本地 application.yml 把 springdoc 配置全写死,用本地覆盖干掉了注入。顺便把相关的配置说明写进系统设计文档了。 产出: 一篇踩坑博客 + 配置修复。 二、给接口文档分了三个层 之前所有微服务的接口都在一个组里,前端要看、后端也要看、微服务 Feign 调用也混在一起。这次给每个服务分了三个 Swagger 分组: 前端接口(给前端的) 后台接口(给管理后台的) 内部接口(给其他微服务调用的) 每个服务各自独立,不再相互干扰。 三个分组的接口在代码上也做了物理隔离——新建了 controller/internal/ 包,只给微服务间 Feign 调用用。顺便把原来跟前端接口冲突的方法也挪过去了,URL 统一加 /v1/internal/ 前缀,从根上避免路由冲突。 三、把前端需要的接口聚合到 BFF 层 之前前端写个页面经常要调好几个微服务,商品详情页调了 5 次接口。虽然 BFF 层之前就已经做了一些聚合(首页聚合、商品详情聚合、下单预览聚合),但管理后台这边基本还是透传状态。 给管理后台 BFF 加了两个聚合接口: 用户编辑页:一次查出用户信息 + 角色列表 + 部门树 + 岗位列表 商品编辑页:一次查出商品详情 + 分类树(品牌和单位的数据等后续补 Feign 客户端) 同时给两个 BFF 服务都加上了 Swagger 文档。以后前端重写,只看这两个 BFF 的文档就够了,不用再翻 8 个微服务的接口。 ...

三月 16, 2023 · 1 分钟 · 127 字 · yaomingye

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 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

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

从写完代码到上线运行

从写完代码到上线运行: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

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

时序数据库在物联网日志系统中的设计与实践:从数据结构到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

阿里云短信接入:双 Provider + Mock 验证码实战

阿里云短信接入 第1步:目标说明 — 别在生产环境调试短信 发送短信验证码是登录/注册流程的核心环节。但对接阿里云短信服务时有两个现实问题: 2024 年后个人资质基本申请不到官方短信签名和模板,审核周期长还不一定过 开发调试时不可能真发短信,每条几分钱不说,频繁发送会被运营商拦截 Mall 项目从这两个痛点出发,设计了一套"双 Provider + Mock 开关"的短信架构: 生产环境:用 dysmsapi(阿里云官方短信 SDK),需要企业资质 个人测试:用 dypnsapi(阿里云号码验证服务),个人账号可申请 本地开发:Mock 模式跳过真发,固定验证码 123456 目标是把这套架构讲清楚,读者照着做能在 30 分钟内完成短信接入。 第2步:前置条件 条件 要求 验证/获取方式 阿里云账号 已实名认证 aliyun.com 注册 AccessKey 已创建 RAM 用户,获取 AK/SK 阿里云控制台 → RAM 访问控制 → 创建 AccessKey 签名和模板(dysmsapi) 企业资质,审核通过 阿里云短信服务控制台(个人很难申请) 号码验证服务(dypnsapi) 个人账号可开通 阿里云号码验证服务控制台 ⚠️ 新手提示:dysmsapi 和 dypnsapi 是阿里云的两个不同产品。dysmsapi 是传统短信服务,需要申请签名和模板;dypnsapi 是号码验证服务,提供预置的短信模板(验证码、通知等),个人资质就能用。本教程两种都讲,读者根据自己的资质选一种即可。 第3步:环境搭建 添加 Maven 依赖 <!-- 方案1:官方短信 SDK --> <dependency> <groupId>com.aliyun</groupId> <artifactId>alibabacloud-dysmsapi20170525</artifactId> <version>3.0.0</version> </dependency> <!-- 方案2:号码验证服务 SDK(个人可用) --> <dependency> <groupId>com.aliyun</groupId> <artifactId>alibabacloud-dypnsapi20170525</artifactId> <version>1.0.8</version> </dependency> 两个依赖都加也没问题,项目通过 @ConditionalOnProperty 在运行时选一个生效,不会冲突。 ...

一月 13, 2023 · 5 分钟 · 878 字 · yaomingye
Cat Radio