阿里云短信接入:双 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

Flyway 数据库迁移:告别手工执行 SQL 脚本

Flyway 数据库迁移 第1步:目标说明 — 从 38 个手工 SQL 脚本说起 Mall 商城项目的 README 里有一句坦诚的自我检讨: SQL 脚本丢在 sql/ 目录手工执行,没有 Flyway / Liquibase。无法追踪某台机器跑过哪些 DDL,回滚靠猜。 打开 sql/feature_1.0.1/ 目录一看——38 个 SQL 文件,命名靠日期: create_table_2024_01_05.sql create_table_2024_01_29.sql alter_table_2024_02_27.sql alter_table_2024_05_12.sql alter_table_2024_09_26.sql ... 每次上线,开发人员手动连上数据库,挑出"这次要跑的"脚本,逐个执行。脚本里还夹杂了手工更新历史数据的 DML: use mall_db; alter table mall_product add column `cover_url` varchar(200) DEFAULT NULL COMMENT '封面图片url'; -- 更新历史数据 update mall_product p inner join mall_product_photo m on p.id = m.product_id set p.cover_url = m.url where m.type=1 and m.is_del=0; -- 别忘了还有分库 use mall_db_order_0; alter table order_trade_item_0 add column `cover_url` varchar(200) ... alter table order_trade_item_1 add column `cover_url` varchar(200) ... use mall_db_order_1; alter table order_trade_item_0 add column `cover_url` varchar(200) ... alter table order_trade_item_1 add column `cover_url` varchar(200) ... 这种模式下会发生什么,写过的人都懂: ...

一月 12, 2023 · 5 分钟 · 1029 字 · yaomingye

Knife4j 接口文档从配置到上线

Knife4j 接口文档 第1步:目标说明 — 打造可交互的 API 文档 后端写完接口,前端过来问"这个参数什么意思"“返回字段有哪些"“能不能让我直接调一下看看效果”——这种场景写过的都懂。 Swagger 就是来解决这个问题的。它能根据代码里的注解自动生成接口文档页面,前端直接在页面上看字段说明、调接口、看返回,不用再追着后端问。而 Knife4j 是 Swagger 的增强 UI,比原生 Swagger UI 好看得多,还支持离线文档导出、全局参数设置、接口排序等实用功能。 本教程基于 Mall 商城项目的真实配置,从零开始搭建一套 Knife4j + Swagger 接口文档,目标是让读者看完就能在自己的项目里用起来。 最终效果:访问 Knife4j 页面,能看到按模块分组的接口列表,点开任意接口能看到请求参数、响应示例,还能直接在页面上填入 Authorization 请求头,在线调试接口。 第2步:前置条件 开始之前,先确认项目环境满足以下条件。 条件 要求 验证命令 JDK 1.8+ java -version Maven 3.6+ mvn -v Spring Boot 2.x 查看 pom.xml 中 spring-boot-starter-parent 版本 现有 Spring Boot Web 项目 已有 Controller 项目中存在 @RestController 类 ⚠️ 新手提示:Knife4j 3.0.2 基于 Springfox 3.0.0,兼容 Spring Boot 2.x。如果是 Spring Boot 3.x 项目,需要使用 knife4j-openapi3-spring-boot-starter 4.x 版本,注解包名也从 io.swagger.annotations 变为 io.swagger.v3.oas.annotations,差异较大,本教程不涉及。 ...

一月 11, 2023 · 6 分钟 · 1108 字 · yaomingye

一个商城项目的结构化日志改造实录

结构化日志改造实录 第1步:目标说明 — 结构化日志到底解决什么问题 某开发者接手了一个 Spring Boot 商城项目的维护。项目跑得挺稳,直到某天凌晨收到告警——短信发送失败了,但翻遍日志找不到任何记录,因为 catch(Exception e) 的块是空的。 这就是非结构化日志的典型场景:日志看似写了,但关键信息全丢了。 结构化日志(Structured Logging)不是一门新技术,而是一种日志编写规范。它的核心目标只有一句话: 让日志既可以被人快速理解,也可以被机器(ELK、Loki、Splunk)精确检索。 本次教程通过一个真实商城项目的日志审计和改造过程,教会读者: 如何识别团队代码中的日志反模式 如何用 SLF4J 的参数化语法替代字符串拼接 如何配置 logback 实现 dev 控制台 + prod 文件持久化 的双环境策略 如何避免异常栈丢失、日志级别混乱等常见坑 完成本教程后,读者能独立完成一个 Spring Boot 项目的日志规范化改造。 第2步:前置条件 — 需要准备什么 开始之前,确保本地环境满足以下条件。 前置项 版本要求 说明 JDK 1.8+ Spring Boot 2.x 编译和运行 Spring Boot 2.x 自带 spring-boot-starter-logging(Logback + SLF4J) Lombok 1.18+ 提供 @Slf4j 注解,免去手写 Logger 声明 Maven 3.6+ 项目构建工具 验证命令: # 检查 JDK java -version # 检查 Maven mvn -version # 检查 Lombok 依赖(在 IDE 中确认 @Slf4j 可用) 📌 前置知识:读者需要了解 Java 异常体系的基本概念(checked / unchecked exception)、Spring Boot 项目的基本结构(Controller → Service → Mapper),以及日志级别 TRACE / DEBUG / INFO / WARN / ERROR 的含义。 ...

一月 10, 2023 · 11 分钟 · 2305 字 · yaomingye

第3步:kubectl 生存手册 —— 开发者每天必敲的命令

kubectl 生存手册 一、目标说明 前三篇文章把概念、YAML、配置都讲完了。这一篇不讲"是什么",只讲 **“怎么查”**和 “怎么排” 。 这是整个系列最实用的一篇——开发者 90% 跟 K8s 打交道的时间,不是写 YAML,而是在这几个命令之间反复横跳: kubectl get → kubectl describe → kubectl logs → kubectl exec — 然后回到 get 读完这篇文章,读者能: 用 4 个核心查看命令快速定位问题 用 3 个交互命令深入容器内部或桥接流量 掌握 9 种 Pod 异常状态的完整诊断流程 用 -o wide/json/yaml 和 --sort-by 提取关键信息 建立"从现象到根因"的排查肌肉记忆 二、前置条件 前置条件 要求 本地 K8s 环境可用 kubectl cluster-info 正常 有几个 Pod 在跑 前几篇文章的 my-first-app 即可 理解 Pod / Deployment / Service 是什么 至少知道它们是干什么的 三、环境准备 沿用前面的 Namespace,确认有资源在跑: ...

一月 8, 2023 · 11 分钟 · 2166 字 · yaomingye

第2步:让 Pod 活得久一点 —— 探针、资源和配置注入实战

让 Pod 活得久一点 一、目标说明 上一篇文章成功部署了第一个 K8s 应用。但现实是——Pod 不会永远乖乖 Running。第二天打开监控一看:一个 Pod 被 OOMKilled,一个在 CrashLoopBackOff 无限重启,还有一个 Pending 了 3 小时没人管。 这篇文章要解决的就是:怎么让 Pod 活得久、死得明白、配置配得清楚。 读完这篇文章,读者能: 区分三种探针的适用场景,写出正确的探针配置 给容器设置合理的 resources 限制,避免 OOMKilled 和 CPU 被偷 掌握环境变量注入的 3 种方式及其选型标准 用 Volume Mount 把配置文件挂进 Pod 看懂 Pod 最常见的 6 种异常状态及其排查方向 二、前置条件 前置条件 要求 验证命令 已完成第 1 步 本地 K8s 能正常 deploy kubectl get deploy -n my-first-app 理解 Pod 基本概念 知道 Pod 里跑容器 看一眼第 0 步速查表即可 理解 Deployment 基本概念 知道 replicas、selector 看一眼第 1 步 Deployment YAML 即可 三、环境准备 沿用第 1 步的环境,先重新部署一遍做基准: ...

一月 7, 2023 · 8 分钟 · 1551 字 · yaomingye

第1步:写出你的第一个 K8s 应用

动手!部署你的第一个 K8s 应用 一、目标说明 上一篇文章把 Docker 和 K8s 的概念地图铺开了。这篇文章要做的是:真正动手,在本地 K8s 集群上部署一个完整的应用。 读完这篇文章,读者能: 验证本地 K8s 环境是否可用 写出一个完整的 Deployment YAML(并理解每一行在说什么) 写出 Service 让 Pod 可以稳定访问 用 ConfigMap 和 Secret 把配置从镜像里拆出来 用 kubectl apply 把整套东西一键部署 通过 kubectl port-forward 在浏览器里访问应用 二、前置条件 前置条件 要求 验证命令 Docker Desktop 已安装 4.x+ docker version Kubernetes 已开启 Docker Desktop Settings → Kubernetes → Enable Kubernetes kubectl cluster-info kubectl 已安装 Docker Desktop 自带 kubectl version --client 上一篇的概念理解 知道 Image / Container / Pod / Deployment / Service 是什么 脑子过一遍层级:Image → Container → Pod → Deployment 如果 kubectl cluster-info 输出类似以下内容,说明环境就绪: ...

一月 6, 2023 · 7 分钟 · 1422 字 · yaomingye

谁说了算——Raft选举、心跳与故障检测在Nacos/Dubbo中的应用

谁说了算 前两篇讲了一个道理:网络和时钟不可靠 → 必须做取舍 → CAP 把取舍定了性。那具体怎么做取舍呢? 如果集群里只有一台机器——不存在一致性问题——所有写操作都在同一块硬盘上——谁先谁后清清楚楚。但只有一台机器的代价是——这台机器宕机——系统全挂。所以需要多台机器——而多台机器就需要一个机制来决定“谁的版本算数”。 这个机制在分布式系统里有一个正式的名字——共识算法(Consensus Algorithm)。Raft 是目前工程界最广泛使用的共识算法——不是因为它理论上最完美——而是因为它可以让人看得懂。 📌 前置知识:建议先读上篇 CAP 定理——理解 CP vs AP 的区别。Raft 是典型的 CP 实现——本文的 Raft 部分主要解释它如何实现 C(一致性)。 一、为什么要有人"说了算"——分布式写操作的困境 先看一个最简单的集群:三台机器——每台都存一份数据——都可以接受写请求。 客户端写入 x=1 → 节点 A 收到——更新本地 x=1 客户端写入 x=2 → 节点 B 收到——更新本地 x=2 (几乎同时——两个客户端连到了两个不同的节点) A 认为 x=1——B 认为 x=2——到底 x 是多少? 两者各自都认为自己的数据正确——没有人有权限说"听我的"——这就是分布式系统里最核心的问题——没有单点权威——写操作需要协调。 flowchart TD start["两个客户端——两个写请求——\n到达两个不同节点"]:::startEnd start --> c1["客户端 1 → 节点 A\nSET x=1"]:::data start --> c2["客户端 2 → 节点 B\nSET x=2"]:::data c1 --> conflict["节点 A:x=1\n节点 B:x=2\n⚡ 冲突——x 到底等于几?"]:::highlight c2 --> conflict conflict --> naive["最简单的方案:\n规定只有一台机器能接受写——\n这台机器叫 Leader"]:::data naive --> next_q["新问题:Leader 宕机了呢?\n谁当新 Leader?\n怎么告诉大家?"]:::condition classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2px,color:#fce7f3,font-weight:bold; classDef data fill:#052e16,stroke:#16a34a,stroke-width:1.5px,color:#bbf7d0,font-weight:bold; classDef highlight fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; classDef condition fill:#2a1147,stroke:#a855f7,stroke-width:1.5px,color:#ede9fe,font-weight:bold; Raft 要解决的就是这两个问题合在一起:(1) 选出一个大家都认可的 Leader——(2) Leader 挂了以后——自动选出新 Leader。 ...

一月 3, 2023 · 4 分钟 · 844 字 · yaomingye

MySQL 实战优化:从 EXPLAIN 到 NULL 陷阱

从 EXPLAIN 到 NULL 陷阱——优化其实有章可循 📌 前置知识:这篇是系列最后一篇,面向日常开发的实战视角。前四篇的理论基础——B+树、索引结构、MVCC、锁机制——这篇会直接引用而不重复展开。建议至少读过第一篇 B+树索引体系再看这篇。 1. EXPLAIN:优化器的自白 EXPLAIN 是 SQL 优化的第一工具。它不会替你优化 SQL,但它告诉你 MySQL 打算怎么优化你的 SQL——用了哪个索引、扫描多少行、做了什么额外操作。理解了它的输出,慢查询的根因通常一目了然。 EXPLAIN SELECT * FROM users WHERE name = 'Zhang' AND age > 20 ORDER BY id; 输出如下(省略部分列): +----+------+---------------+------+---------+-------+------+-------------------+ | id | type | possible_keys | key | key_len | ref | rows | Extra | +----+------+---------------+------+---------+-------+------+-------------------+ | 1 | ref | idx_name | idx | 102 | const | 120 | Using index cond | +----+------+---------------+------+---------+-------+------+-------------------+ 逐字段解读: ...

十二月 31, 2022 · 6 分钟 · 1208 字 · yaomingye

事务消息 + 本地消息表 + 生产踩坑

事务消息 + 本地消息表 📖 前置阅读:本文是分布式事务系列的第四篇——假设你已经理解了 CAP/BASE 理论、Seata AT 的 undo_log 机制、TCC 的 Try/Confirm/Cancel 三阶段和 Saga 的补偿链。如果这些概念还陌生——先读 分布式事务本质——CAP、BASE 与四大方案、Seata AT 模式——undo_log 与二阶段原理 和 TCC + Saga——补偿型分布式事务。 一、⚡ 同步方案的瓶颈——为什么还需要异步方案 先回顾前面三篇文章我们做了什么: Seata AT:下单 → 扣库存 → 扣余额——三个操作在一个 @GlobalTransactional 中——同步执行 TCC:Try 预留 → Confirm 确认 → Cancel 回滚——三个阶段——同步执行 Saga:正向执行 → 失败逆补偿——协调者串联——同步执行 它们有一个共同特征:调用方要等所有分支都执行完——才返回结果。 order-service 调用 product-service 扣库存: → 发起 RPC 调用 → 等待 product-service 处理 → 等待 product-service 返回结果 → 拿到结果——继续下一步 如果 product-service 很慢——比如库存要查 3 个 Redis + 2 个 DB: → order-service 的线程就等着 → 线程池撑爆 → 整个链路超时 同步方案的根本矛盾:事务参与方的响应时间——直接影响调用方的吞吐量。 ...

十二月 30, 2022 · 17 分钟 · 3414 字 · yaomingye
Cat Radio