手机短信验证码架构设计方案
目录一、手机发送短信验证码架构图二、为什么要有图形验证码三、Kaptcha验证码注意事项四、短信验证码60s防刷方案设计4.1 验证码存储格式设计4.2 防刷机制详解4.3 多层防刷策略五、发送短信验证码流程图六、图形验证码编码最佳实践七、短信验证码发送接口开发与防刷机制代码实现一、手机发送短信验证码架构图注意事项成本控制防刷机制:限制同一手机号60秒内只能发送一次每日上限:限制同一手机号每天5次,防止恶意攻击余额预警:设置余额预警,余额低于100元时发送通知用户体验发送速度:短信一般3-5秒到达,如果超过30秒未收到,建议提示用户重发失败重试:短信发送失败时,可以重试1-2次,但不要过多重试错误提示:发送失败时给出清晰的错误提示,如短信服务繁忙,请稍后再试二、为什么要有图形验证码很多人会白嫖你的短信次数一天会白消耗你非常多的短信次数所以增加图形验证码可以有效的防止这种情况发生。三、Kaptcha验证码注意事项前端调用示例验证码URL添加时间戳参数t,防止浏览器缓存点击图片刷新验证码,提升用户体验发送短信前必须先输入图形验证码安全性服务端校验:图形验证码必须在服务端生成和校验,不能在前端校验一次性使用:验证码校验后立即删除,防止重复使用有效期限制:5分钟有效期,过期自动失效不区分大小写:使用equalsIgnoreCase比较,提升用户体验用户体验字符简单:只使用数字0-9,不使用容易混淆的字母(如O和0,I和l)无干扰线:去掉干扰线,降低识别难度点击刷新:支持点击图片刷新验证码错误提示:验证码错误时给出清晰提示性能优化缓存Producer:Kaptcha Producer对象创建成本高,应该缓存复用异步生成:如果并发高,可以考虑异步生成验证码Redis过期:设置合理的过期时间,避免Redis内存占用过高四、短信验证码60s防刷方案设计4.1 验证码存储格式设计Redis Value格式:验证码_时间戳示例:123456_1705838400000验证码部分:用于登录时校验时间戳部分:用于防刷检查为什么要存储时间戳防刷控制:计算距离上次发送的时间间隔过期管理:虽然Redis有TTL,但时间戳可以做更精细的控制日志追踪:方便排查问题时知道验证码何时发送4.2 防刷机制详解// 从Redis获取缓存值:验证码_时间戳 String cacheValue 123456_1705838400000; String[] parts cacheValue.split(_); String code parts[0]; // 验证码:123456 long sendTime Long.parseLong(parts[1]); // 发送时间戳 // 计算时间间隔 long now System.currentTimeMillis(); long interval now - sendTime; // 距离上次发送的毫秒数 // 如果小于60秒,拒绝发送 if (interval 60 * 1000) { return JsonData.buildResult(BizCodeEnum.CODE_LIMITED); }4.3 多层防刷策略前端防刷:发送后按钮禁用60秒,防止误点击后端防刷:检查Redis中上次发送时间,硬性限制60秒图形验证码:发送前必须先通过图形验证码,防止机器人每日上限:(可选)限制同一手机号每天最多发送5次五、发送短信验证码流程图六、图形验证码编码最佳实践验证码工具类Slf4j Component public class CaptchaUtil { private final Producer captchaProducer; private final StringRedisTemplate redisTemplate; private static final String CAPTCHA_KEY_PREFIX captcha:; private static final int CAPTCHA_EXPIRE_MINUTES 5; public CaptchaUtil(StringRedisTemplate redisTemplate) { this.redisTemplate redisTemplate; this.captchaProducer createCaptchaProducer(); } /** * 生成图形验证码并返回图片流 * param identifier 手机号或邮箱 * param type 验证码类型login/register * return 验证码图片的字节数组 */ public byte[] generateCaptchaImage(String identifier, String type) { // 生成验证码文本 String captchaText captchaProducer.createText(); // 将验证码保存到Redis String captchaKey getCaptchaKey(identifier, CheckCodeTypeEnum.valueOf(type).name()); log.info(保存图形验证码的key{}, captchaKey); redisTemplate.opsForValue().set(captchaKey, captchaText, CAPTCHA_EXPIRE_MINUTES, TimeUnit.MINUTES); // 生成验证码图片 BufferedImage image captchaProducer.createImage(captchaText); // 转换为字节数组 try { ByteArrayOutputStream outputStream new ByteArrayOutputStream(); ImageIO.write(image, jpg, outputStream); return outputStream.toByteArray(); } catch (IOException e) { throw new RuntimeException(生成验证码图片失败, e); } } /** * 校验验证码 * param identifier 手机号或邮箱 * param type 验证码类型login/register */ public void verifyCaptcha(String identifier, String captcha, String type) { String captchaKey getCaptchaKey(identifier, type); log.info(验证图形验证码的key{}, captchaKey); String savedCaptcha redisTemplate.opsForValue().get(captchaKey); if (savedCaptcha null) { throw new RuntimeException(验证码已过期); } if (!savedCaptcha.equalsIgnoreCase(captcha)) { throw new RuntimeException(验证码错误); } // 验证通过后删除验证码 redisTemplate.delete(captchaKey); } private String getCaptchaKey(String identifier, String type) { return CAPTCHA_KEY_PREFIX type : identifier; } private Producer createCaptchaProducer() { Properties properties new Properties(); // 图片宽度 properties.setProperty(kaptcha.image.width, 120); // 图片高度 properties.setProperty(kaptcha.image.height, 40); // 字体大小 properties.setProperty(kaptcha.textproducer.font.size, 30); // 字体颜色 properties.setProperty(kaptcha.textproducer.font.color, black); // 字符长度 properties.setProperty(kaptcha.textproducer.char.length, 4); // 字体 properties.setProperty(kaptcha.textproducer.font.names, Arial); // 只使用数字 properties.setProperty(kaptcha.textproducer.char.string, 0123456789); // 字符间距 properties.setProperty(kaptcha.textproducer.char.space, 4); // 背景颜色 properties.setProperty(kaptcha.background.clear.from, white); properties.setProperty(kaptcha.background.clear.to, white); // 干扰线颜色 properties.setProperty(kaptcha.noise.color, gray); // 干扰线数量 properties.setProperty(kaptcha.noise.impl, com.google.code.kaptcha.impl.NoNoise); Config config new Config(properties); DefaultKaptcha defaultKaptcha new DefaultKaptcha(); defaultKaptcha.setConfig(config); return defaultKaptcha; } }RestController RequiredArgsConstructor RequestMapping(/api/v1/account) Tag(name 账号服务, description 账号服务接口) public class AccountController { private final CaptchaUtil captchaUtil; private final AccountService accountService; /** * 获取图形验证码 * * param identifier 手机号或邮箱 * param type 验证码类型login/register * return 验证码图片的字节数组 */ RequestMapping(value /captcha, produces MediaType.IMAGE_JPEG_VALUE) public byte[] captcha(RequestParam(value identifier, required true) String identifier, RequestParam(value type, required true) String type) { return captchaUtil.generateCaptchaImage(identifier, type); }七、短信验证码发送接口开发与防刷机制代码实现请求实体类/** * 发送验证码请求参数 */ Data Schema(description 发送验证码请求参数) public class SendCheckCodeReq { Schema(description 手机号或邮箱, example 13800138000) private String identifier; Schema(description 图形验证码, example 8264) private String captcha; Schema(description 验证码类型:login-登录,register-注册, example login) private String type; }Override public JsonData sendCheckCode(SendCheckCodeReq req) { log.info(发送短信验证码,手机号:{},类型:{}, req.getIdentifier(), req.getType()); // 1. 校验图形验证码 try { captchaUtil.verifyCaptcha(req.getIdentifier(), req.getCaptcha(), req.getType()); } catch (Exception e) { log.error(图形验证码校验失败:{}, e.getMessage()); return JsonData.buildError(e.getMessage()); } // 2. 构建Redis Key String cacheKey String.format(RedisKeyManager.CHECK_CODE_KEY, req.getType(), req.getIdentifier()); String cacheValue redisTemplate.opsForValue().get(cacheKey); // 3. 检查60秒内是否重复发送 if (cacheValue ! null !cacheValue.isEmpty()) { String[] parts cacheValue.split(_); long sendTime Long.parseLong(parts[1]); long now System.currentTimeMillis(); // 如果距离上次发送不足60秒,拒绝发送 if (now - sendTime 60 * 1000) { log.info(重复发送验证码,时间间隔:{} 秒, (now - sendTime) / 1000); return JsonData.buildResult(BizCodeEnum.CODE_LIMITED); } } // 4. 生成6位数字验证码 String smsCode CommonUtil.generateNumberCode(); String value smsCode _ System.currentTimeMillis(); // 5. 存储到Redis(10分钟有效) redisTemplate.opsForValue().set(cacheKey, value, CODE_EXPIRED, TimeUnit.MILLISECONDS); // 6. 判断是邮箱还是手机号 if (CommonUtil.isEmail(req.getIdentifier())) { // TODO: 邮箱验证码发送逻辑 log.info(邮箱验证码发送功能待实现); return JsonData.buildError(邮箱验证码功能暂未开放); } else if (CommonUtil.isPhone(req.getIdentifier())) { // 7. 发送短信验证码 try { log.info(短信验证码已生成,手机号:{},验证码:{}, req.getIdentifier(), smsCode); /* SmsDTO request SmsDTO.builder() .mobile(req.getIdentifier()) .param(smsCode) .build(); String response smsClient.sendSms(request); log.info(发送短信,结果{},response);*/ return JsonData.buildSuccess(验证码已发送请注意查收); } catch (Exception e) { log.error(发送短信失败, e); throw new RuntimeException(发送短信失败: e.getMessage()); } } return JsonData.buildResult(BizCodeEnum.CODE_TO_ERROR); }

相关新闻

context-mode:人机协同中的动态上下文感知系统

context-mode:人机协同中的动态上下文感知系统

1. 项目概述:这不是一个功能开关,而是一套认知操作系统“context-mode”这个词最近在技术社区、产品讨论组和设计工作坊里频繁冒头,但它既不是某个新发布的API,也不是某家大厂刚开源的框架。我第一次听到它,是在帮某高…

2026/10/19 12:31:18 阅读更多 →
G-Helper 从入门到调校指南:免费替代 Armoury Crate,一个 exe 管好华硕 ROG 笔记本

G-Helper 从入门到调校指南:免费替代 Armoury Crate,一个 exe 管好华硕 ROG 笔记本

G-Helper 从入门到调校指南:免费替代 Armoury Crate,一个 exe 管好华硕 ROG 笔记本 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Stri…

2026/10/19 12:31:18 阅读更多 →
MATLAB手写三层神经网络:可调试、可导出权重的全连接实现

MATLAB手写三层神经网络:可调试、可导出权重的全连接实现

简介:本资源是一套面向神经网络初学者的MATLAB实践程序集,聚焦算法实现与工程落地,适用于机器学习入门、课程设计及小规模预测建模等场景。压缩包共12个文件,包含4个MATLAB数据文件(.mat)、4个Excel数据集&…

2026/10/19 12:31:18 阅读更多 →

最新新闻

两参数威布尔分布风速建模:MATLAB参数估计与拟合优度检验实战

两参数威布尔分布风速建模:MATLAB参数估计与拟合优度检验实战

简介:这份MATLAB小程序面向风能行业工程师、风电项目研究者及学习风速统计建模的学生,用于对风电场实测风速数据进行两参数威布尔分布拟合与参数估计。压缩包内共1个文件,为weibull1.m脚本,体积约436B,属于轻量级可直接…

2026/10/19 13:14:38 阅读更多 →
遥感影像与矢量点配准后像素坐标提取:从GeoTransform到GDAL实战

遥感影像与矢量点配准后像素坐标提取:从GeoTransform到GDAL实战

做遥感的人应该都遇到过这种事:手里有一幅影像,上面还有一批矢量点,可能是外业踏勘的采样点、精度验证用的检查点、或者深度学习样本里要用的目标中心点。现在要把这些点对应的像素行列号提出来,也就是“像素坐标”。很多人第一反…

2026/10/19 13:14:38 阅读更多 →
open-code-review:可审计、可编程的LLM代码审查CLI工具

open-code-review:可审计、可编程的LLM代码审查CLI工具

1. 这不是又一个“AI写代码”工具:open-code-review 的真实定位与设计哲学你搜“open-code-review”,大概率会撞上一堆带“CLI”“LLM”“Git”标签的零散讨论,甚至夹杂着“Codex CLI”“ZCode CLI”“Trae CLI”这类名字相似但目标迥异的项目…

2026/10/19 13:14:38 阅读更多 →
Java加密与SSL/TLS快速参考:核心原理与HTTPS实战指南

Java加密与SSL/TLS快速参考:核心原理与HTTPS实战指南

1. 内容整体设计与思路拆解 1.1 为什么需要一份“快速参考指南” 先聊点实际的。搞Java开发这几年,我见过太多同事在加密和SSL这块踩坑了——不是不会写代码,而是每次都要现查文档、翻博客、试半天才能凑出一段能跑的代码。尤其是碰到对接第三方系统、上…

2026/10/19 13:14:38 阅读更多 →
基于LLM的智能客服系统架构设计与排障实践

基于LLM的智能客服系统架构设计与排障实践

去年我主导了一个智能客服平台从零到一的设计与落地,整套系统基于LLM(大语言模型)搭建,上线后稳定运行了三个多月,扛住了一年中最大的活动流量,也把各种诡异问题从日志里一个个捞出来解决掉。这篇文章不是泛…

2026/10/19 13:14:38 阅读更多 →
开源SECS/GEM协议栈实战:从HSMS状态机到S6F11事件上报

开源SECS/GEM协议栈实战:从HSMS状态机到S6F11事件上报

简介:SECS(半导体设备通信标准)开源实现库,面向半导体设备自动化领域的开发者与研究人员,用于简化设备控制器与生产线工具之间的数据交换与通信开发。资源包共11个文件,约39KB,以test测试脚本、…

2026/10/19 13:13:37 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →