SpringBoot项目实战:集成腾讯云短信SDK实现验证码登录 📅 2026/7/15 6:40:37 1. 腾讯云短信服务基础配置在开始集成腾讯云短信SDK之前我们需要先完成一些基础配置工作。这些配置是后续所有操作的前提条件就像盖房子前需要先打好地基一样。首先登录腾讯云控制台找到短信服务并开通。这个过程非常简单就像在电商平台开通一个新服务。开通后你会看到一个控制面板这里就是我们管理短信功能的主界面。接下来需要创建短信签名。签名相当于你的短信身份证会显示在用户收到的短信开头。比如【腾讯科技】这样的前缀。创建签名时需要选择类型网站、APP、公众号或小程序并上传相关证明材料。我建议使用公众号类型因为审核通过率较高。记得在申请说明中写清楚用途比如用于用户登录验证。然后是创建短信模板。模板就是短信的正文内容其中可以包含变量。比如验证码登录常用的模板您的验证码是{1}请于{2}分钟内填写。创建模板时要注意内容不能包含任何营销信息否则很难通过审核。我通常会准备3-4个不同用途的模板比如登录验证、密码重置等。最后要获取API密钥。在访问管理-API密钥管理中你可以看到SecretId和SecretKey。这两个密钥非常重要相当于你调用腾讯云API的账号密码一定要妥善保管。建议为短信服务单独创建一组密钥不要使用主账号的密钥。2. SpringBoot项目初始化现在我们可以开始搭建SpringBoot项目了。我习惯使用Spring Initializr快速生成项目骨架选择Web、Redis和Lombok这几个常用依赖。首先在pom.xml中添加腾讯云短信SDK依赖dependency groupIdcom.tencentcloudapi/groupId artifactIdtencentcloud-sdk-java/artifactId version3.1.714/version /dependency注意不要使用4.x版本虽然版本号更高但不是最新稳定版。我踩过这个坑4.x版本有些接口不兼容。然后在application.properties中配置腾讯云参数# 腾讯云短信配置 tencent.sms.secret-id你的SecretId tencent.sms.secret-key你的SecretKey tencent.sms.sdk-app-id你的应用ID tencent.sms.sign-name你的签名内容 tencent.sms.template-id你的模板ID tencent.sms.endpointsms.tencentcloudapi.com tencent.sms.regionap-guangzhou为了安全起见我建议把这些敏感信息放在单独的配置文件中或者使用环境变量。我在实际项目中遇到过配置文件泄露的问题现在都会格外小心。3. 短信服务核心实现有了基础配置我们可以开始编写短信发送的核心代码了。我习惯把这类第三方服务封装成独立的工具类。首先创建一个配置类来加载属性Data Component ConfigurationProperties(prefix tencent.sms) public class SmsProperties { private String secretId; private String secretKey; private String sdkAppId; private String signName; private String templateId; private String endpoint; private String region; }然后是短信工具类的主要实现Service Slf4j public class SmsService { Autowired private SmsProperties smsProperties; Autowired private RedisTemplateString, String redisTemplate; public boolean sendVerificationCode(String phoneNumber) { try { // 生成6位随机验证码 String code RandomStringUtils.randomNumeric(6); // 初始化腾讯云客户端 Credential cred new Credential(smsProperties.getSecretId(), smsProperties.getSecretKey()); HttpProfile httpProfile new HttpProfile(); httpProfile.setEndpoint(smsProperties.getEndpoint()); ClientProfile clientProfile new ClientProfile(); clientProfile.setHttpProfile(httpProfile); SmsClient client new SmsClient(cred, smsProperties.getRegion(), clientProfile); SendSmsRequest req new SendSmsRequest(); // 设置请求参数 req.setSmsSdkAppId(smsProperties.getSdkAppId()); req.setSignName(smsProperties.getSignName()); req.setTemplateId(smsProperties.getTemplateId()); req.setPhoneNumberSet(new String[]{86 phoneNumber}); req.setTemplateParamSet(new String[]{code, 5}); // 验证码和过期时间(分钟) // 发送短信 SendSmsResponse resp client.SendSms(req); log.info(短信发送响应: {}, SendSmsResponse.toJsonString(resp)); // 存储验证码到Redis5分钟过期 redisTemplate.opsForValue().set( sms:code: phoneNumber, code, 5, TimeUnit.MINUTES); return true; } catch (Exception e) { log.error(发送短信验证码失败, e); return false; } } }这个实现有几个关键点使用Redis存储验证码并设置过期时间避免永久有效对手机号做了86前缀处理符合国际标准加入了完善的日志记录方便排查问题使用try-catch捕获所有异常保证服务稳定性4. 验证码校验与防刷策略发送验证码只是第一步我们还需要实现验证码的校验功能。同时要考虑防止恶意刷接口的安全问题。首先实现验证码校验方法public boolean verifyCode(String phoneNumber, String code) { String key sms:code: phoneNumber; String correctCode redisTemplate.opsForValue().get(key); if (StringUtils.isEmpty(correctCode)) { return false; // 验证码不存在或已过期 } if (!correctCode.equals(code)) { return false; // 验证码不匹配 } // 验证通过后删除验证码防止重复使用 redisTemplate.delete(key); return true; }然后是防刷策略的实现。我通常会采用以下几种方式组合IP限流限制同一IP的发送频率手机号限流限制同一手机号的发送频率图形验证码在发送短信前要求输入图形验证码在SpringBoot中可以使用Spring AOP实现这些限制Aspect Component Slf4j public class SmsLimitAspect { Autowired private RedisTemplateString, String redisTemplate; Pointcut(execution(* com.example.service.SmsService.sendVerificationCode(..))) public void smsSendPointcut() {} Around(smsSendPointcut()) public Object aroundSend(ProceedingJoinPoint joinPoint) throws Throwable { String phoneNumber (String) joinPoint.getArgs()[0]; String ip getCurrentIp(); // 获取当前请求IP的方法 // IP限流检查 String ipKey sms:limit:ip: ip; Long ipCount redisTemplate.opsForValue().increment(ipKey); redisTemplate.expire(ipKey, 1, TimeUnit.HOURS); if (ipCount 20) { throw new RuntimeException(操作过于频繁请稍后再试); } // 手机号限流检查 String phoneKey sms:limit:phone: phoneNumber; Long phoneCount redisTemplate.opsForValue().increment(phoneKey); redisTemplate.expire(phoneKey, 1, TimeUnit.HOURS); if (phoneCount 5) { throw new RuntimeException(该手机号发送次数过多); } return joinPoint.proceed(); } }5. 完整登录流程实现现在我们可以把这些组件组合起来实现完整的验证码登录流程。首先创建控制器RestController RequestMapping(/api/auth) public class AuthController { Autowired private SmsService smsService; PostMapping(/send-code) public ResponseEntity? sendCode(RequestParam String phone) { boolean success smsService.sendVerificationCode(phone); if (success) { return ResponseEntity.ok().build(); } else { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } PostMapping(/login) public ResponseEntity? login(RequestParam String phone, RequestParam String code) { if (smsService.verifyCode(phone, code)) { // 验证通过生成JWT token等登录逻辑 String token generateToken(phone); return ResponseEntity.ok(token); } else { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } }为了提升用户体验我通常会做以下优化返回统一的JSON格式响应提供详细的错误信息记录登录日志支持多种登录方式验证码密码6. 常见问题与解决方案在实际项目中我遇到过不少问题这里分享几个典型的问题1签名审核不通过解决方案确保签名内容简洁明了证明材料清晰可见。如果是公众号签名公众号名称要和签名一致。问题2短信发送延迟解决方案腾讯云短信通常秒级到达如果延迟明显可以检查网络连接是否正常是否触发了频率限制短信模板是否包含敏感词问题3验证码校验失败可能原因用户输入错误Redis配置问题导致验证码未存储手机号格式不一致如带86和不带86我的排查步骤通常是检查Redis中是否存在对应key比较存储的验证码和用户输入的验证码检查手机号格式是否统一问题4接口被刷除了前面提到的限流措施还可以增加图形验证码使用行为验证如滑动验证监控异常请求并自动封禁7. 性能优化与最佳实践经过多个项目的实践我总结了一些优化经验连接池优化腾讯云SDK底层使用HTTP连接可以配置连接池提高性能HttpProfile httpProfile new HttpProfile(); httpProfile.setMaxConnections(100); // 最大连接数 httpProfile.setConnectionTimeout(3000); // 连接超时时间异步发送对于不需要即时反馈的场景可以使用异步发送Async public void asyncSendVerificationCode(String phoneNumber) { sendVerificationCode(phoneNumber); }短信模板复用设计通用的模板通过参数控制不同场景// 使用同一个模板通过不同参数实现多种功能 req.setTemplateParamSet(new String[]{code, 5, 登录});监控与告警集成监控系统当短信发送失败率超过阈值时告警多区域部署如果用户分布广泛可以考虑使用腾讯云的多区域部署选择离用户最近的区域备用通道重要业务可以考虑集成多个短信服务商当一个失败时自动切换8. 测试与部署建议在项目上线前充分的测试是必不可少的。我通常采用以下测试策略单元测试测试工具类的各个方法Test public void testSendVerificationCode() { boolean result smsService.sendVerificationCode(13800138000); assertTrue(result); }集成测试测试完整的登录流程Test public void testLoginFlow() { // 发送验证码 ResponseEntity? sendResponse authController.sendCode(13800138000); assertEquals(200, sendResponse.getStatusCodeValue()); // 模拟用户输入验证码 String code getCodeFromRedis(13800138000); // 调用登录接口 ResponseEntity? loginResponse authController.login(13800138000, code); assertEquals(200, loginResponse.getStatusCodeValue()); }压力测试使用JMeter模拟高并发场景验证系统稳定性部署时建议使用配置中心管理敏感信息避免硬编码做好Redis的持久化配置防止重启丢失验证码设置合理的JVM参数避免内存溢出准备回滚方案万一出现问题可以快速恢复9. 实际项目中的经验分享在最近的一个电商项目中我们遇到了高峰期短信发送量激增的问题。经过分析发现主要瓶颈在Redis的写入性能上。最终我们采取了以下优化措施使用Redis集群分散压力对验证码存储进行分片按手机号尾号分散到不同key引入本地缓存作为一级缓存减少Redis访问优化序列化方式使用更高效的序列化算法调整后的架构能够支持每秒上万次的验证码请求平稳度过了双十一高峰。另一个经验是关于国际化的。当项目需要支持海外用户时要注意手机号可能需要不同前缀不是86短信模板需要多语言版本要考虑时区问题避免半夜给用户发短信不同国家的短信法规不同需要合规性审查10. 扩展功能思路基础功能实现后可以考虑扩展更多实用功能语音验证码对于收不到短信的情况提供语音验证码备用方案一键登录集成运营商的免密登录能力营销短信在用户允许的情况下发送促销信息通知类短信订单状态变更、物流信息等数据统计分析短信发送量、成功率等指标例如实现语音验证码只需要调用腾讯云的另一套APIpublic boolean sendVoiceCode(String phoneNumber) { // 类似短信验证码的实现只是调用不同的API // ... }在项目中我通常会把短信、语音、邮件等通知方式抽象成统一的接口方便业务调用public interface VerificationService { boolean sendCode(String target, CodeType type); boolean verify(String target, String code); }这样业务代码就不需要关心具体使用哪种验证方式提高了系统的灵活性和可维护性。