Java案例怎么实现短信验证码?

wen java案例 15

Java案例怎么实现短信验证码?完整流程与核心源码解析

目录导读

  • 为什么要实现短信验证码?它解决了什么问题?
  • 短信验证码在Java项目中的整体架构与选型(阿里云vs腾讯云)
  • 核心实现步骤:前端触发 → 后端生成 → 第三方API调用 → 校验
  • 实战源码:从Controller到Service完整代码(附带关键注释)
  • 高频问题解答:验证码生成策略、有效期设置、防刷机制
  • 如何对这段代码进行SEO优化与性能调优?

为什么要实现短信验证码?它解决了什么问题?

问:为什么现在几乎所有的App或网站都要求用户输入短信验证码?

Java案例怎么实现短信验证码?

答:因为“短信验证码”是目前平衡安全性用户体验的最佳方案之一,它主要解决以下三个问题:

  1. 确认手机号真实有效:只有持有该手机号的用户才能收到验证码,完成注册或登录。
  2. 防机器人刷接口:在登录、注册、找回密码等高敏感操作中,加入验证码可以有效拦截自动化脚本攻击。
  3. 增加一道门槛:即便数据库被拖库,黑客也无法直接登录,因为他们没有手机短信验证码。

核心逻辑:用户填写手机号 → 系统生成随机6位数字/字母 → 调用第三方短信服务商API → 将验证码发送至用户手机 → 用户填写 → 后端对比校验。


短信验证码在Java项目中的整体架构与选型

问:直接写代码就能发短信了吗?需要选择什么供应商?

答:不能,Java代码本身无法直接发送短信,必须依赖第三方短信服务商提供的SDK,主流的服务商有:

服务商 特点 费用(参考)
阿里云短信 国内普及最广,SDK文档清晰,支持国际短信 约0.045元/条
腾讯云短信 新用户赠送1000条,接口稳定 约0.043元/条
云片网 门槛低,小团队常用 约0.038元/条

本文示例以阿里云短信为例,因为它生态最成熟。

整体架构图
前端(Vue/React)Java后端ControllerService层调用阿里云SDK写入Redis(缓存验证码)返回成功/失败


核心实现步骤(附完整代码)

问:具体从项目结构到代码,每一步该怎么做?

引入依赖(pom.xml)

<!-- 阿里云短信SDK -->
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>4.6.3</version>
</dependency>
<!-- Redis 缓存验证码 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置类(application.yml)

aliyun:
  sms:
    access-key-id: LTAI5tJxxxxxxxxx  # 请替换为自己的
    access-key-secret: 8Z0xxxxxxxxxx
    sign-name: 你的签名
    template-code: SMS_123456789    # 模板ID
    region-id: cn-hangzhou

Controller层 —— 接收手机号

@RestController
@RequestMapping("/api/sms")
public class SmsController {
    @Autowired
    private SmsService smsService;
    @PostMapping("/send")
    public Result<?> sendCode(@RequestParam String phone) {
        // 参数校验:必须为11位数字
        if (!phone.matches("^1[3-9]\\d{9}$")) {
            return Result.error("手机号格式错误");
        }
        boolean flag = smsService.sendSmsCode(phone);
        return flag ? Result.success("验证码已发送") : Result.error("发送失败,请稍后重试");
    }
    @PostMapping("/verify")
    public Result<?> verifyCode(@RequestParam String phone, @RequestParam String code) {
        boolean valid = smsService.verify(phone, code);
        return valid ? Result.success("验证通过") : Result.error("验证码错误或已过期");
    }
}

Service层 —— 核心业务逻辑

@Service
public class SmsService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Value("${aliyun.sms.access-key-id}")
    private String accessKeyId;
    @Value("${aliyun.sms.access-key-secret}")
    private String accessKeySecret;
    @Value("${aliyun.sms.sign-name}")
    private String signName;
    @Value("${aliyun.sms.template-code}")
    private String templateCode;
    @Value("${aliyun.sms.region-id}")
    private String regionId;
    // 发送验证码
    public boolean sendSmsCode(String phone) {
        // 1. 生成随机6位数字验证码
        String code = String.format("%06d", new Random().nextInt(999999));
        // 2. 将验证码存储到Redis,有效期5分钟
        redisTemplate.opsForValue().set("sms:code:" + phone, code, 5, TimeUnit.MINUTES);
        // 3. 调用阿里云API发送短信
        try {
            DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
            IAcsClient client = new DefaultAcsClient(profile);
            CommonRequest request = new CommonRequest();
            request.setSysMethod(MethodType.POST);
            request.setSysDomain("dysmsapi.aliyuncs.com");
            request.setSysVersion("2017-05-25");
            request.setSysAction("SendSms");
            request.putQueryParameter("PhoneNumbers", phone);
            request.putQueryParameter("SignName", signName);
            request.putQueryParameter("TemplateCode", templateCode);
            request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}");
            CommonResponse response = client.getCommonResponse(request);
            // 实际生产需解析 response 中的 Code 字段
            return response.getHttpResponse().isSuccess();
        } catch (Exception e) {
            log.error("短信发送失败: {}", e.getMessage());
            return false;
        }
    }
    // 校验验证码
    public boolean verify(String phone, String inputCode) {
        String key = "sms:code:" + phone;
        String cacheCode = redisTemplate.opsForValue().get(key);
        if (cacheCode == null) {
            return false; // 已过期或未发送
        }
        // 验证后无论成功与否,建议删除,防止重复使用
        redisTemplate.delete(key);
        return cacheCode.equals(inputCode);
    }
}

前端调用示例(JavaScript/axios)

// 发送验证码
axios.post('/api/sms/send', { phone: '13800138000' })
  .then(res => { console.log(res.data.message); });
// 验证
axios.post('/api/sms/verify', { phone: '13800138000', code: '123456' })
  .then(res => { alert(res.data.message); });

高频问题解答:防刷、有效期、生成策略

问:如果用户频繁点击获取验证码,怎么办?

答:这是面试常见题,必须添加防刷机制:

  • 限流:同一手机号每分钟最多发送1次,每天最多5次,可以在Redis中加一个计数器(sms:limit:phone:139xxxxxxxx)。
  • 图形验证码:在点击“发送验证码”之前,要求先通过图形验证码或滑块验证,减少机器操作。
  • IP限制:同一IP地址每小时最多发送50条。

问:验证码的有效期设为多少合理? 答:通常为5分钟,太短用户来不及输入,太长(如30分钟)会增加被暴力破解的风险,使用Redis的TTL自动过期是最佳实现方式。

问:验证码应该用纯数字还是数字+字母? 答:推荐纯6位数字,原因:

  1. 用户输入方便,尤其移动端。
  2. 对于验证码场景,“一次有效”已经足够安全,纯数字组合(1,000,000种)在5分钟内暴力破解是不现实的(除非被人肉盯上)。

SEO优化与性能调优建议

问:这篇文章是关于Java案例的,但我想让我的项目也有好的排名,技术方面要注意什么?

  1. URL设计:如果你的项目中有关于短信验证码的接口,建议RESTful风格,如 /api/sms/send-code 而不是 /send?type=1
  2. 页面加载速度:JWT或Redis存储验证码时,尽量使用连接池,减少每次连接Redis的开销。
  3. 错误提示:不要在前端返回“验证码错误”或“手机号未注册”,应统一返回“验证失败”,防止黑客通过返回信息猜解用户状态。
  4. 日志埋点:发送验证码成功/失败、验证成功/失败都应记录日志,便于排查与监控(也利于SEO数据统计工具如百度统计)。

Java实现短信验证码的核心没有想象中复杂:生成随机数 → 存Redis → 调用第三方API → 校验,但生产环境真正做好的关键在于:

  • 选择稳定的服务商(阿里云/腾讯云)
  • 做好防刷与限流
  • 处理好缓存与过期策略

如果你完全按照本文的代码和思路实现,不仅能通过面试关,还能直接部署上线,如果你已经踩过坑,欢迎在评论区分享你的“血泪史”——比如某次阿里云鉴权失败导致验证码全挂的教训。

抱歉,评论功能暂时关闭!