spring-boot | 集成短信验证码服务

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 目前,手机号对于我们的日常生活有着非常重要的作用,特别是手机号码实名认证以后,手机号如同你的身份证一样记录着我们的各种信息。所以短信验证码作为手机号的验证方式也显得尤为重要。因此,很多公司或网站用短信验证码来验证我们的身份信息。

目前,手机号对于我们的日常生活有着非常重要的作用,特别是手机号码实名认证以后,手机号如同你的身份证一样记录着我们的各种信息。所以短信验证码作为手机号的验证方式也显得尤为重要。因此,很多公司或网站用短信验证码来验证我们的身份信息。常见的使用场景有:登录注册、信息修改、异常登录、找回密码等操作。

今天给大家分享一下如何接入当前应用比较广泛的阿里云短信服务平台和容联云短信平台,其实每个短信平台接入方式都大同小异。

首先我们需要去各家短信平台申请属于我们自己的开发者账号,容联云通讯会有一定数量的免费短信条数用来测试,但是免费短信不可以自定义发送内容的模板,充值一定金额后可以申请。阿里云是后付费模式,可以使用短信自定义的短信模板或者签名。

申请好了以后我们就要这些需要的信息添加到我们的配置文件中。

pom.xml添加依赖

阿里云短信提供了通过maven方式添加依赖,容联云通讯则需要自己手动去下载jar包添加到工程目录中。

      <dependency>
            <groupId>com.aliyun.mns</groupId>
            <artifactId>aliyun-sdk-mns</artifactId>
            <version>1.1.8</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-sms</artifactId>
            <version>3.0.0-rc1</version>
        </dependency>

验证码配置文件smscode.properties:

#容联云通讯短信验证码
user.smsCode.accountSid = #
user.smsCode.accountToken = #
user.smsCode.serverIp = app.cloopen.com
user.smsCode.serverPort = 8883
user.smsCode.appId = #

#短信验证码失效时间(单位秒)
user.smsCode.expires = 60
#短信验证码长度
user.smsCode.smsCodelen = 6

#阿里云AccessId
user.aliyun.smsCode.accessId=#
#阿里云AccessKey
user.aliyun.smsCode.accessKey=#
#访问MNS服务的接入地址
user.aliyun.smsCode.mnsEndpoint=#
#发送短信使用的主题
user.aliyun.smsCode.topic=sms.topic-cn-hangzhou
#发送短信使用的签名
user.aliyun.smsCode.signName=竞技世界
#注册模板Code
user.aliyun.smsCode.regTemplateCode=#
#登录模板Code
user.aliyun.smsCode.loginTemplateCode=#
#忘记密码模板Code
user.aliyun.smsCode.forgetTemplateCode=#
#绑定手机模板Code
user.aliyun.smsCode.bindTemplateCode=#
#免注账号升级模板Code
user.aliyun.smsCode.upgradeTemplateCode=#

阿里云短信配置自动加载

**
 * 
* @ClassName: AliyunSmsCodeProperties 
* @Description: TODO(阿里云短信配置) 
* @author huasc
* @date 2017年8月29日 下午1:44:51 
*
 */
@Configuration
//自动加载配置文件前缀是user.aliyun.smsCode的配置文件
@ConfigurationProperties(prefix = "user.aliyun.smsCode")
//配置文件的启用条件 当user.smsService.component的值为aliyunSmcodeService时 该配置文件才会被启用
@ConditionalOnProperty(name = "user.smsService.component",havingValue = "aliyunSmcodeService")
//配置文件地址
@PropertySource(value = "classpath:smscode.properties", encoding = "UTF-8")
public class AliyunSmsCodeProperties {

    private String accessId;

    private String accessKey;

    private String mnsEndpoint;

    private String topic;

    private String signName;

    private String regTemplateCode;

    private String loginTemplateCode;

    private String forgetTemplateCode;

    private String bindTemplateCode;
    
    private String upgradeTemplateCode;

    private CloudTopic tp;

    public String getAccessId() {
        return accessId;
    }

    public void setAccessId(String accessId) {
        this.accessId = accessId;
    }

    public String getAccessKey() {
        return accessKey;
    }

    public void setAccessKey(String accessKey) {
        this.accessKey = accessKey;
    }

    public String getMnsEndpoint() {
        return mnsEndpoint;
    }

    public void setMnsEndpoint(String mnsEndpoint) {
        this.mnsEndpoint = mnsEndpoint;
    }

    public String getTopic() {
        return topic;
    }

    public void setTopic(String topic) {
        this.topic = topic;
    }

    public String getSignName() {
        return signName;
    }

    public void setSignName(String signName) {
        this.signName = signName;
    }

    public String getRegTemplateCode() {
        return regTemplateCode;
    }

    public void setRegTemplateCode(String regTemplateCode) {
        this.regTemplateCode = regTemplateCode;
    }

    public String getLoginTemplateCode() {
        return loginTemplateCode;
    }

    public void setLoginTemplateCode(String loginTemplateCode) {
        this.loginTemplateCode = loginTemplateCode;
    }

    public String getForgetTemplateCode() {
        return forgetTemplateCode;
    }

    public void setForgetTemplateCode(String forgetTemplateCode) {
        this.forgetTemplateCode = forgetTemplateCode;
    }

    public String getBindTemplateCode() {
        return bindTemplateCode;
    }

    public void setBindTemplateCode(String bindTemplateCode) {
        this.bindTemplateCode = bindTemplateCode;
    }


    @Bean("mnsClient")
    MNSClient createClient() {
        CloudAccount account = new CloudAccount(this.accessId, this.accessKey, this.mnsEndpoint);
        MNSClient client = account.getMNSClient();
        this.client = client;
        return client;

    }

    @Bean
    CloudTopic cloudTopic(MNSClient mnsClient) {
        CloudTopic tp = mnsClient.getTopicRef(this.topic);
        this.tp = tp;
        return tp;
    }

    private MNSClient client;

    @PreDestroy
    public void destroy() {
        client.close();
    }

    public String getUpgradeTemplateCode() {
        return upgradeTemplateCode;
    }

    public void setUpgradeTemplateCode(String upgradeTemplateCode) {
        this.upgradeTemplateCode = upgradeTemplateCode;
    }

}


容联云短信配置自动加载类:


@ConfigurationProperties(prefix = "user.smsCode")
@ConditionalOnProperty(name = "user.smsService.component",havingValue = "ronglianService")
@PropertySource(value = "classpath:smscode.properties", encoding = "UTF-8")
@Configuration
public class RonglianSmsCodeProperties {

    private String accountSid;

    private String accountToken;

    private String appId;

    private String serverIp;

    private String serverPort;
    

    public String getAccountSid() {
        return accountSid;
    }

    public void setAccountSid(String accountSid) {
        this.accountSid = accountSid;
    }

    public String getAccountToken() {
        return accountToken;
    }

    public void setAccountToken(String accountToken) {
        this.accountToken = accountToken;
    }

    public String getAppId() {
        return appId;
    }

    public void setAppId(String appId) {
        this.appId = appId;
    }

    public String getServerIp() {
        return serverIp;
    }

    public void setServerIp(String serverIp) {
        this.serverIp = serverIp;
    }

    public String getServerPort() {
        return serverPort;
    }

    public void setServerPort(String serverPort) {
        this.serverPort = serverPort;
    }

    @Bean 
    public CCPRestSmsSDK createPhoneClient() {
        CCPRestSmsSDK phoneClient = new CCPRestSmsSDK();
        phoneClient.setAccount(accountSid, accountToken);
        phoneClient.init(serverIp, serverPort);
        phoneClient.setAppId(appId);
        return phoneClient;
    }
    

开启服务:

指定使用哪个短信服务,只需把响应的注释打开就可以啦。

#阿里云短信验证码平台
user.smsService.component = aliyunSmcodeService
#容联云通讯短信验证码平台
#user.smsService.component = ronglianService

这样所有的关于验证码的配置都完成了,下面是service层,我是将验证码存入到redis中并且设置有效时间,
对短信验证码进行匹配验证。如果一致的话,则将改验证码从redis库中删除掉。同时,存入的过程中设置了不同类型的key,这样通过redis客户端来查看的话就会分层显示,特别清晰。

公共部分

public abstract class SmsCodeServiceImpl implements SmsCodeService{
    
    @Autowired
    private PassportRepository passportRepository;
    
    
    abstract JedisPool getJedisPool ();
    
    @Override
    public Boolean checkSmsCode(String phoneNum, int codeType, String smsCode) {
        String smsCodeKey;
        Boolean flag = false;
        Jedis jedis = null;
        try {
            if (SmsCode.SmsCodeType.REG.value() == codeType) {
                smsCodeKey = CacheKeys.getSmsCodeRegKey(phoneNum);
            } else if (SmsCode.SmsCodeType.LOGIN.value() == codeType) {
                smsCodeKey = CacheKeys.getSmsCodeLoginKey(phoneNum);
            } else if (SmsCode.SmsCodeType.FORGET.value() == codeType) {
                smsCodeKey = CacheKeys.getSmsCodeForget(phoneNum);
            } else if (SmsCode.SmsCodeType.BIND.value() == codeType) {
                smsCodeKey = CacheKeys.getSmsCodeBind(phoneNum) ;
            } else if (SmsCode.SmsCodeType.UPGRADE.value() == codeType) {
                smsCodeKey = CacheKeys.getSmsCodeUpgrade(phoneNum);
            } else {
                return flag;
            }
            jedis = getJedisPool().getResource();
            String code = jedis.get(smsCodeKey);
            if(StringUtils.isBlank(code)){
                return flag;
            }else if (code.equals(smsCode)) {
                jedis.del(smsCodeKey);
                flag = true;
            }
            
        }  finally {
            jedis.close();
        }
        return flag;
    }
    /**
     * 检查手机号是否已经被使用
     */
    @Override
    public Boolean checkPhoneNumberIsUsed(String phoneNum){
        Passport passport = passportRepository.findByPhoneNum(phoneNum);
        if(passport == null){
            return false;
        }else{
            return true;
        }
    }
}


阿里云短信服务Service

@Service("aliyunSmcodeService")
@ConditionalOnProperty(name = "user.smsService.component", havingValue = "aliyunSmcodeService")
public class AliyunSmsCodeServiceImpl extends SmsCodeServiceImpl {

    @Value("${user.smsCode.expires}")
    private int expires;

    @Value("${user.smsCode.smsCodelen}")
    private int smsCodelen;

    @Autowired
    private JedisPool jedisPool;

    @Autowired
    private AliyunSmsCodeProperties aliyunSmsCodeProperties;

    @Autowired
    private CloudTopic topic;
    
    @Override
    JedisPool getJedisPool() {
        return jedisPool ;
    }

    /**
     * 通过阿里云发送短信验证码
     */
    @Transactional
    public SendMcodeResponse sendSmsCode(String phoneNum, int codeType) {

        RawTopicMessage msg = new RawTopicMessage();
        msg.setMessageBody("sms-message");
        MessageAttributes messageAttributes = new MessageAttributes();
        BatchSmsAttributes batchSmsAttributes = new BatchSmsAttributes();
        batchSmsAttributes.setFreeSignName(aliyunSmsCodeProperties.getSignName());
        // 短信模板code
        String templateCode = null;
        String smsCodeKey = null;

        if (SmsCode.SmsCodeType.REG.value() == codeType) {
            templateCode = aliyunSmsCodeProperties.getRegTemplateCode();
            smsCodeKey = CacheKeys.getSmsCodeRegKey(phoneNum);
        } else if (SmsCode.SmsCodeType.LOGIN.value() == codeType) {
            templateCode = aliyunSmsCodeProperties.getLoginTemplateCode();
            smsCodeKey = CacheKeys.getSmsCodeLoginKey(phoneNum);
        } else if (SmsCode.SmsCodeType.FORGET.value() == codeType) {
            templateCode = aliyunSmsCodeProperties.getForgetTemplateCode();
            smsCodeKey = CacheKeys.getSmsCodeForget(phoneNum);
        } else if (SmsCode.SmsCodeType.BIND.value() == codeType) {
            templateCode = aliyunSmsCodeProperties.getBindTemplateCode();
            smsCodeKey = CacheKeys.getSmsCodeBind(phoneNum);
        }else if (SmsCode.SmsCodeType.UPGRADE.value() == codeType) {
            templateCode = aliyunSmsCodeProperties.getUpgradeTemplateCode();
            smsCodeKey = CacheKeys.getSmsCodeUpgrade(phoneNum);
        }

        batchSmsAttributes.setTemplateCode(templateCode);
        // 设置发送短信所使用的模板中参数对应的值(第一个参数是验证码,第二个参数是过期时间)
        BatchSmsAttributes.SmsReceiverParams smsReceiverParams = new BatchSmsAttributes.SmsReceiverParams();
        String code = RandomStringUtils.randomNumeric(smsCodelen);
        smsReceiverParams.setParam("code", code);
        smsReceiverParams.setParam("time", String.valueOf(expires / 60));
        // 接收短信的手机号码
        batchSmsAttributes.addSmsReceiver(phoneNum, smsReceiverParams);
        messageAttributes.setBatchSmsAttributes(batchSmsAttributes);
        Jedis jedis = jedisPool.getResource();
        SendMcodeResponse r = null;
        try {
            // 发送消息
            TopicMessage ret = topic.publishMessage(msg, messageAttributes);
            // 将验证码信息存放于redis中并设置过期时间
            jedis.set(smsCodeKey, code);
            jedis.expire(smsCodeKey, expires);
            r = SendMcodeResponse.newBuilder().setMcode(code).setExpires(expires)
                    .setR(Result.newBuilder().setCode(Result.StatusType.SUCCESS_VALUE).setMsg("OK").build()).build();
        } catch (ServiceException se) {
            throw new ServiceException(se.getErrorCode(),se.getMessage());
        } catch (Exception e) {
            r = SendMcodeResponse.newBuilder().setMcode(code).setExpires(expires)
                    .setR(Result.newBuilder().setCode(Result.StatusType.ERROR_VALUE).setMsg(e.getMessage()).build()).build();
        } finally {
            jedis.close();
        }
        return r;
    }

    

}

容联云通讯短信Service

@Service("ronglianService")
@ConditionalOnProperty(name = "user.smsService.component", havingValue = "ronglianService")
public class RonglianSmsCodeServiceImpl  extends SmsCodeServiceImpl {

    @Autowired
    private CCPRestSmsSDK client;

    @Autowired
    private JedisPool jedisPool;

    @Value("${user.smsCode.expires}")
    private int expires;

    @Value("${user.smsCode.smsCodelen}")
    private int smsCodelen;
    

    @Override
    JedisPool getJedisPool() {
        return jedisPool;
    }

    /**
     * 发送短信验证码
     * @throws AccountException 
     */
    @Transactional
    @Override
    public SendMcodeResponse sendSmsCode(String phoneNum, int codeType) throws AccountException {
        String code = RandomStringUtils.randomNumeric(smsCodelen);
        HashMap<String, Object> message = client.sendTemplateSMS(phoneNum, String.valueOf(codeType),
                new String[] { code, String.valueOf(expires/60) });
        SendMcodeResponse r = null;
        if ("000000".equals(message.get("statusCode"))) {
            Jedis jedis = jedisPool.getResource();
            try {
                String smsCodeKey = null;
                if (SmsCode.SmsCodeType.REG.value() == codeType) {
                    smsCodeKey = CacheKeys.getSmsCodeRegKey(phoneNum);
                } else if (SmsCode.SmsCodeType.LOGIN.value() == codeType) {
                    smsCodeKey = CacheKeys.getSmsCodeLoginKey(phoneNum);
                } else if (SmsCode.SmsCodeType.FORGET.value() == codeType) {
                    smsCodeKey = CacheKeys.getSmsCodeForget(phoneNum);
                } else if (SmsCode.SmsCodeType.BIND.value() == codeType) {
                    smsCodeKey = CacheKeys.getSmsCodeBind(phoneNum);
                }
                jedis.set(smsCodeKey, code);
                jedis.expire(smsCodeKey, expires);
                r = SendMcodeResponse.newBuilder().setMcode(code).setExpires(expires)
                        .setR(Result.newBuilder().setCode(Result.StatusType.SUCCESS_VALUE).setMsg("OK").build()).build();
            } catch (Exception e) {
                r = SendMcodeResponse.newBuilder().setMcode(code).setExpires(expires)
                        .setR(Result.newBuilder().setCode(Result.StatusType.ERROR_VALUE).setMsg(e.getMessage()).build()).build();
            } finally {
                jedis.close();
            }
        } else {
            throw new AccountException(String.valueOf(message.get("statusMsg")),AccountException.ExCode.SEND_EXCEPTION.value());
        }
        return r;
    }

}

CacheKeys

public class CacheKeys {
    private final static String SSOTOKEN_PREFIX = "users:login:ssotoken:" ;
    
    private final static String SMSCODE_REG_PREFIX = "users:smscode:reg:" ;
    
    private final static String SMSCODE_LOGIN_PREFIX = "users:smscode:login:" ;
    
    private final static String SMSCODE_FORGET_PREFIX = "users:smscode:forget:" ;
    
    private final static String SMSCODE_BIND_PREFIX = "users:smscode:bind:" ;
    
    private final static String SMSCODE_UPGRADE_PREFIX = "users:smscode:upgrade:" ;
    
    private final static String USER_ALLTOKEN_PREFIX = "users:login:all:" ;
    
    private final static String EMAIL_FORGET_PREFIX = "users:emailcode:forget:" ;
    
    private final static String EMAIL_BIND_PREFIX = "users:emailcode:bind:" ;
    
    private final static String QRCODE_UUID_PREFIX = "users:qrcode:uuid:" ;
    
    private final static String QRCODE_SKEY_PREFIX = "users:qrcode:skey:" ;
    
    public static String getSsoTokenKey(String ssotoken) {
        return SSOTOKEN_PREFIX+ssotoken ;
    }
    
    public static String getSmsCodeRegKey(String phoneNum){
        return SMSCODE_REG_PREFIX+phoneNum ;
    }
    public static String getSmsCodeLoginKey(String phoneNum){
        return SMSCODE_LOGIN_PREFIX+phoneNum ;
    }
    
    public static String getSmsCodeForget(String phoneNum){
        return SMSCODE_FORGET_PREFIX+phoneNum ;
    }
    
    public static String getSmsCodeBind(String phoneNum){
        return SMSCODE_BIND_PREFIX+phoneNum ;
    }
    
    public static String getSmsCodeUpgrade(String phoneNum){
        return SMSCODE_UPGRADE_PREFIX+phoneNum ;
    }
    
    public static String getUserAllKey(String accountId){
        return USER_ALLTOKEN_PREFIX+"usr-"+accountId ;
    }
    
    public static String getEmailCodeForget(String email){
        return EMAIL_FORGET_PREFIX+email ;
    }
    
    public static String getEmailCodeBind(String email){
        return EMAIL_BIND_PREFIX+email ;
    }
    
    public static String getQrCodeUuidKey(String uuid){
        return QRCODE_UUID_PREFIX+uuid ;
    }
    
    public static String getQrCodeSkeyKey(String skey){
        return QRCODE_SKEY_PREFIX+skey ;
    }
}

到这里就全部完成了短信短信验证码的功能。


(完)

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2月前
|
存储 数据可视化 Java
基于MicrometerTracing门面和Zipkin实现集成springcloud2023的服务追踪
Sleuth将会停止维护,Sleuth最新版本也只支持springboot2。作为替代可以使用MicrometerTracing在微服务中作为服务追踪的工具。
158 1
|
2月前
|
Java Maven Docker
gitlab-ci 集成 k3s 部署spring boot 应用
gitlab-ci 集成 k3s 部署spring boot 应用
|
24天前
|
机器学习/深度学习 人工智能 自然语言处理
Voice-Pro:开源AI音频处理工具,集成转录、翻译、TTS等一站式服务
Voice-Pro是一款开源的多功能音频处理工具,集成了语音转文字、文本转语音、实时翻译、YouTube视频下载和人声分离等多种功能。它支持超过100种语言,适用于教育、娱乐和商业等多个领域,为用户提供一站式的音频处理解决方案,极大地提高工作效率和音频处理的便捷性。
97 10
Voice-Pro:开源AI音频处理工具,集成转录、翻译、TTS等一站式服务
|
25天前
|
XML Java API
Spring Boot集成MinIO
本文介绍了如何在Spring Boot项目中集成MinIO,一个高性能的分布式对象存储服务。主要步骤包括:引入MinIO依赖、配置MinIO属性、创建MinIO配置类和服务类、使用服务类实现文件上传和下载功能,以及运行应用进行测试。通过这些步骤,可以轻松地在项目中使用MinIO的对象存储功能。
|
27天前
|
消息中间件 Java Kafka
什么是Apache Kafka?如何将其与Spring Boot集成?
什么是Apache Kafka?如何将其与Spring Boot集成?
65 5
|
29天前
|
消息中间件 Java Kafka
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
43 1
|
1月前
|
安全 测试技术 数据安全/隐私保护
原生鸿蒙应用市场开发者服务的技术解析:从集成到应用发布的完整体验
原生鸿蒙应用市场开发者服务的技术解析:从集成到应用发布的完整体验
|
2月前
|
存储 Java 开发工具
【三方服务集成】最新版 | 阿里云OSS对象存储服务使用教程(包含OSS工具类优化、自定义阿里云OSS服务starter)
阿里云OSS(Object Storage Service)是一种安全、可靠且成本低廉的云存储服务,支持海量数据存储。用户可通过网络轻松存储和访问各类文件,如文本、图片、音频和视频等。使用OSS后,项目中的文件上传业务无需在服务器本地磁盘存储文件,而是直接上传至OSS,由其管理和保障数据安全。此外,介绍了OSS服务的开通流程、Bucket创建、AccessKey配置及环境变量设置,并提供了Java SDK示例代码,帮助用户快速上手。最后,展示了如何通过自定义starter简化工具类集成,实现便捷的文件上传功能。
【三方服务集成】最新版 | 阿里云OSS对象存储服务使用教程(包含OSS工具类优化、自定义阿里云OSS服务starter)
|
2月前
|
开发框架 .NET API
Windows Forms应用程序中集成一个ASP.NET API服务
Windows Forms应用程序中集成一个ASP.NET API服务
109 9
|
2月前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
246 1