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

本文涉及的产品
云数据库 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
目录
相关文章
|
1月前
|
前端开发 关系型数据库 MySQL
IDEA集成Docker插件打包服务镜像与运行【附Docker命令汇总】
IDEA集成Docker插件打包服务镜像与运行【附Docker命令汇总】
|
1月前
|
Web App开发 监控 Java
|
29天前
|
NoSQL Java Redis
小白版的springboot中集成mqtt服务(超级无敌详细),实现不了掐我头!!!
小白版的springboot中集成mqtt服务(超级无敌详细),实现不了掐我头!!!
230 1
|
2月前
|
安全 Java 数据库
后端进阶之路——万字总结Spring Security与数据库集成实践(五)
后端进阶之路——万字总结Spring Security与数据库集成实践(五)
|
3月前
|
Dubbo Java 应用服务中间件
Spring Boot Dubbo 构建分布式服务
Spring Boot Dubbo 构建分布式服务
47 0
|
1月前
|
SpringCloudAlibaba Dubbo Java
SpringCloud Alibaba集成Dubbo实现远程服务间调用
SpringCloud Alibaba集成Dubbo实现远程服务间调用
|
1月前
|
SpringCloudAlibaba Java 网络架构
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(七)Spring Cloud Gateway服务网关
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(七)Spring Cloud Gateway服务网关
81 0
|
3月前
|
NoSQL Java Redis
Spring Boot集成Redisson详细介绍
Redisson是一个用于Java的分布式和高可用的Java对象的框架,它基于Redis实现。在Spring Boot应用程序中集成Redisson可以帮助我们更轻松地实现分布式锁、分布式对象、分布式集合等功能。本文将介绍如何在Spring Boot项目中集成Redisson,并展示一些基本用法。
462 2
Spring Boot集成Redisson详细介绍
|
30天前
|
Java 数据库连接 数据库
Spring Boot整合MyBatis Plus集成多数据源轻松实现数据读写分离
Spring Boot整合MyBatis Plus集成多数据源轻松实现数据读写分离
26 2
|
1月前
|
消息中间件 SpringCloudAlibaba Java
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
778 0