我用的短信平台是阿里云的,需要付费购买服务,购买地址:https://common-buy.aliyun.com/?spm=5176.8195934.907839.sms6.312c4183mzE9Yb&&commodityCode=newdysmsbag#/buy
付费完成后,首先申请短信签名和短信模板:https://help.aliyun.com/document_detail/55327.html?spm=a2c4g.11186623.6.549.huzd56。
短信签名:根据用户属性来创建符合自身属性的签名信息。企业用户需要上传相关企业资质证明,个人用户需要上传证明个人身份的证明。注意:短信签名需要审核通过后才可以使用。
短信模板:短信模板,即具体发送的短信内容。短信模板可以支持验证码、短信通知、推广短信、国际/港澳台消息四种模式。验证码和短信通知,通过变量替换实现个性短信定制。推广短信不支持在模板中添加变量。短信模板需要审核通过后才可以使用。
短信示例:【阿里云】 验证码${number},您正进行支付宝的身份验证,打死不告诉别人!这里的短信签名:阿里云,短信模板: 验证码${number},您正进行支付宝的身份验证,打死不告诉别人!
最后获取 asscessKeyId 和 accessKeySecret 。结合阿里云提供的开发者文档即可进行接口开发,短信开发api文档:https://help.aliyun.com/product/44282.html?spm=a2c4g.750001.6.1.T84wBi
一、安装redis
下载地址:http://redis.io/download,下载最新稳定版本。
在/usr/local创建redis文件夹上传下载的安装包到redis目录下:
安装:
$ tar -zxvf redis-4.0.9.tar.gz
$ cd redis-4.0.9
$ make
安装完成后的目录:
$ cd src
$ ./redis-server ../redis.conf
./redis-server 这种方式启动redis 使用的是默认配置。可以通过启动参数告诉redis使用指定配置文件使用下面命令启动。
二、创建maven工程,目录结构如下:
三、pom.xml文件添加redis依赖
<!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
四、application.properties添加redis配置
五、随机生成验证码工具类
public class IdentifyCodeUtil { public static String getRandom() { String num = ""; for (int i = 0; i < 6; i++) { num = num + String.valueOf((int) Math.floor(Math.random() * 9 + 1)); } return num; } }
六、redis缓存配置类
sprringboot启动类Application.java加入注解:@EnableCaching
配置redis采用缓存,设置key和value的序列化方式
package com.jp.tech.applet.ms.sms; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; /** * Redis缓存配置类 * * @author yangfeng */ @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { //缓存管理器 @Bean public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); //设置缓存过期时间 //cacheManager.setDefaultExpiration(20); return cacheManager; } @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) { StringRedisTemplate template = new StringRedisTemplate(factory); setSerializer(template);//设置序列化工具 template.afterPropertiesSet(); return template; } private void setSerializer(StringRedisTemplate template) { @SuppressWarnings({"rawtypes", "unchecked"}) Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setValueSerializer(jackson2JsonRedisSerializer); } }
七、redis接口类
此接口用于连接redis操作生成的验证码,设置过期时间。
public interface IRedisService { /** * 设置key-value * @param key * @param value */ void setKey(String key, String value); /** * 获取key * @param key * @return */ String getValue(String key); /** * 删除key * @param key */ void delete(String key); }
八、redis接口实现类
保存、获取、删除验证码接口实现方法。
import com.jp.tech.applet.ms.sms.service.IRedisService; import org.springframework.data.redis.core.*; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; @Service public class RedisServiceImpl implements IRedisService { @Resource private RedisTemplate redisTemplate; @Override public void setKey(String key, String value) { ValueOperations<String, String> ops = redisTemplate.opsForValue(); ops.set(key, value, 900, TimeUnit.SECONDS);//15分钟过期 } @Override public String getValue(String key) { ValueOperations<String, String> ops = redisTemplate.opsForValue(); return ops.get(key); } @Override public void delete(String key) { redisTemplate.delete(key); } }
九、发送短信接口类
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; import com.aliyuncs.exceptions.ClientException; public interface ISmsService { /** * 发送短信接口 * * @param phoneNums 手机号码 * @param signName 模板签名 * @param templeteCode 模板代码 * @param templateParam 模板替换参数 * @param outId 提供给业务方扩展字段 * @return * @throws ClientException */ SendSmsResponse sendSms(String phoneNums, String signName, String templeteCode, String templateParam, String outId) throws ClientException; /** * 查询短信发送明细 * * @param phoneNumber * @param bizId 业务流水号 * @return * @throws ClientException */ QuerySendDetailsResponse querySendDetails(String phoneNumber, String bizId) throws ClientException; }
十、短信接口实现类
这里我用的是阿里云短信平台,根据提供的demo自己整理后的接口实现。
import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsRequest; import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.profile.DefaultProfile; import com.aliyuncs.profile.IClientProfile; import com.jp.tech.applet.ms.sms.service.ISmsService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.text.SimpleDateFormat; import java.util.Date; @Service public class SmsServiceImpl implements ISmsService { private Logger logger = LoggerFactory.getLogger(this.getClass()); //产品名称:云通信短信API产品,开发者无需替换 static final String product = "Dysmsapi"; //产品域名,开发者无需替换 static final String domain = "dysmsapi.aliyuncs.com"; @Value("${aliyun.sms.accessKeyId}") private String accessKeyId; @Value("${aliyun.sms.accessKeySecret}") private String accessKeySecret; /** * 发送短信接口 * @param phoneNums * @param signName 模板签名 * @param templeteCode 模板代码 * @param templateParam 模板替换参数 * @param outId 提供给业务方扩展字段 * @return * @throws ClientException */ @Override public SendSmsResponse sendSms(String phoneNums, String signName, String templeteCode, String templateParam, String outId) throws ClientException { //可自助调整超时时间 System.setProperty("sun.net.client.defaultConnectTimeout", "10000"); System.setProperty("sun.net.client.defaultReadTimeout", "10000"); //初始化acsClient,暂不支持region化 IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret); DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain); IAcsClient acsClient = new DefaultAcsClient(profile); //组装请求对象-具体描述见控制台-文档部分内容 SendSmsRequest request = new SendSmsRequest(); //必填:待发送手机号 request.setPhoneNumbers(phoneNums); //必填:短信签名-可在短信控制台中找到 request.setSignName(signName);//众评众测 //必填:短信模板-可在短信控制台中找到 request.setTemplateCode(templeteCode);//SMS_136856023 //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为 request.setTemplateParam(templateParam);//{"code":"152745"} //选填-上行短信扩展码(无特殊需求用户请忽略此字段) //request.setSmsUpExtendCode("90997"); //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者 request.setOutId(outId);//zpzc //hint 此处可能会抛出异常,注意catch SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request); acsClient.getAcsResponse(request); return sendSmsResponse; } /** * 查询短信发送明细 * @param phoneNumber * @param bizId * @return * @throws ClientException */ @Override public QuerySendDetailsResponse querySendDetails(String phoneNumber, String bizId) throws ClientException { //可自助调整超时时间 System.setProperty("sun.net.client.defaultConnectTimeout", "10000"); System.setProperty("sun.net.client.defaultReadTimeout", "10000"); //初始化acsClient,暂不支持region化 IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret); DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain); IAcsClient acsClient = new DefaultAcsClient(profile); //组装请求对象 QuerySendDetailsRequest request = new QuerySendDetailsRequest(); //必填-号码 request.setPhoneNumber(phoneNumber); //可选-流水号 request.setBizId(bizId); //必填-发送日期 支持30天内记录查询,格式yyyyMMdd SimpleDateFormat ft = new SimpleDateFormat("yyyyMMdd"); request.setSendDate(ft.format(new Date())); //必填-页大小 request.setPageSize(10L); //必填-当前页码从1开始计数 request.setCurrentPage(1L); //hint 此处可能会抛出异常,注意catch QuerySendDetailsResponse querySendDetailsResponse=acsClient.getAcsResponse(request); return querySendDetailsResponse; } }
十一、controller类
调用redis存入随机生成的验证码,调用短信平台接口发送验证码。
smsService.sendSms(phoneName, "XXXXXX", "XXXXXXX", JSON.toJSONString(codeMap), null)
第一个XXXXX代表申请的短信签名名称,第二个代表申请的短信模板编码,改成自己申请的即可。
import com.alibaba.fastjson.JSON; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; import com.jp.tech.applet.common.constant.Constant; import com.jp.tech.applet.common.result.ResultModel; import com.jp.tech.applet.ms.sms.service.IRedisService; import com.jp.tech.applet.ms.sms.service.ISmsService; import com.jp.tech.applet.ms.sms.util.IdentifyCodeUtil; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.HashMap; import java.util.Map; /** * 短信验证码接口 * * @author yangfeng * @date 2018-06-09 16:02 **/ @RestController @RequestMapping("/smsVerifityCode") public class SmsVerifityCodeController { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Resource private ISmsService smsService; @Resource private IRedisService redisService; /** * 发送短信验证码 * * @param phoneName * @return */ @RequestMapping("/sendMessage") public ResultModel sendMessage(String phoneName) { String identifyCode; //1. 判断是否缓存该账号验证码 String returnCode = redisService.getValue(phoneName + Constant.SMS_LOGIN_IDENTIFY_CODE); if (!StringUtils.isEmpty(returnCode)) { identifyCode = returnCode; } else { identifyCode = IdentifyCodeUtil.getRandom(); redisService.setKey(phoneName + Constant.SMS_LOGIN_IDENTIFY_CODE, identifyCode); } //2.发送短信 Map<String, String> codeMap = new HashMap<>(); codeMap.put("code", identifyCode); SendSmsResponse response; try { response = smsService.sendSms(phoneName, "XXXXXX", "XXXXXXX", JSON.toJSONString(codeMap), null); } catch (Exception e) { logger.error("sendMessage method invoke error: {}",e.getMessage()); return ResultModel.commonError("短信发送失败:" + e.getMessage()); } return ResultModel.success(response); } /** * 判断验证码是否正确 * * @param phoneName * @param identifyCode * @return */ @RequestMapping("/checkIsCorrectCode") public ResultModel checkIsCorrectCode(String phoneName, String identifyCode) { String returnCode = redisService.getValue(phoneName + Constant.SMS_LOGIN_IDENTIFY_CODE); if (!StringUtils.isEmpty(returnCode) && returnCode.equals(identifyCode)) { return ResultModel.success("验证成功"); } return ResultModel.commonError("验证失败"); } }
十二、单元测试service接口
import com.aliyuncs.dysmsapi.model.v20170525.QuerySendDetailsResponse; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; import com.jp.tech.applet.ms.sms.service.ISmsService; import com.jp.tech.applet.web.Application; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.annotation.Resource; @RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) public class SmsTest { @Resource private ISmsService smsService; @Test public void sendSmsTest() throws Exception { SendSmsResponse response = smsService.sendSms("15287XXXXX","XXX","XXXXX","{\"code\":\"152745\"}",""); System.out.println("短信接口返回的数据----------------"); System.out.println("Code=" + response.getCode()); System.out.println("Message=" + response.getMessage()); System.out.println("RequestId=" + response.getRequestId()); System.out.println("BizId=" + response.getBizId()); Thread.sleep(3000L); //查明细 if(response.getCode() != null && response.getCode().equals("OK")) { QuerySendDetailsResponse querySendDetailsResponse = smsService.querySendDetails("15287XXXXX,response.getBizId()); System.out.println("短信明细查询接口返回数据----------------"); System.out.println("Code=" + querySendDetailsResponse.getCode()); System.out.println("Message=" + querySendDetailsResponse.getMessage()); int i = 0; for(QuerySendDetailsResponse.SmsSendDetailDTO smsSendDetailDTO : querySendDetailsResponse.getSmsSendDetailDTOs()) { System.out.println("SmsSendDetailDTO["+i+"]:"); System.out.println("Content=" + smsSendDetailDTO.getContent()); System.out.println("ErrCode=" + smsSendDetailDTO.getErrCode()); System.out.println("OutId=" + smsSendDetailDTO.getOutId()); System.out.println("PhoneNum=" + smsSendDetailDTO.getPhoneNum()); System.out.println("ReceiveDate=" + smsSendDetailDTO.getReceiveDate()); System.out.println("SendDate=" + smsSendDetailDTO.getSendDate()); System.out.println("SendStatus=" + smsSendDetailDTO.getSendStatus()); System.out.println("Template=" + smsSendDetailDTO.getTemplateCode()); } System.out.println("TotalCount=" + querySendDetailsResponse.getTotalCount()); System.out.println("RequestId=" + querySendDetailsResponse.getRequestId()); } } }单元测试通过后,短信平台整合结束。