转自http://coder520.com/
1、发送短信
1.1、设置常量
1.2、对接短信是一个https的接口,所以我们需要在代码中发送https请求,我们使用httpclient包来操作。
1.3、创建HttpcUtil工具类
1.4、由于我们的发短信接口,只要是一个电话我们就发一条短信,存在安全问题,如果不法分子通过抓包知道我们的短信接口,然后每天不断的请求我们的接口,我们都得发送短信,一条一分这样不好。
短信接口安全性解决办法:
1、IP, 通过ip来确定,超过十次,不发验证码
2、手机号,同一个手机号在一定时间内不能不停请求如果超过一定的次数,就不发验证码,这个记录次数没必要存到数据库,存到redis就行。
1.5、
@RequestMapping(value = "/sendVercode")
public ApiResult<String> sendVercode(@RequestBody User user, HttpServletRequest request) {
ApiResult<String> resp = new ApiResult();
try {
userService.sendVercode(user.getMobile(), getIpFromRequest(request));
} catch (MaMaBikeException e) {
//发送失败
log.error(e.getMessage());
resp.setCode(Constants.RESP_STATUS_INTERNAL_ERROR);
resp.setMessage(e.getMessage());
} catch (Exception e) {
// 登录失败,返回失败信息,就不用返回data
// 记录日志
log.error("Fail to send smsVercode", e);
resp.setCode(Constants.RESP_STATUS_INTERNAL_ERROR);
resp.setMessage("内部错误!");
}
return resp;
}
1.6、发送短信的业务层
1、调用产生验证码方法
/**1、生成4位随机验证码**/
String verCode = RandomNumberCode.verCode();
/**
* Author ljs
* Description 生成随机验证码
* Date 2018/10/2 3:00
**/
public class RandomNumberCode {
public static String verCode(){
Random random =new Random();
/**每次生成太长我们去第2到第6位**/
return StringUtils.substring(String.valueOf(random.nextInt()*-10), 2, 6);
}
}
2、把验证码存到redis
需要存的三对
key1(手机号) value(验证码)
key2(ip) value(次数)
key3(手机号+类型) value(次数)1 当前验证码未过期 2 手机号超过当前验证码次数上限 3、ip超过当日验证码上线 0 开始发送验证码短信
我们要想当发送验证码的时候我们需要存啥到redis里,第一个手机号一定要的吧(作为key),第二个验证码也要,第三个过期时间也要,第四个它的ip也要(安全问题),第五个是类型,是为了表明这是注册时候发送的验证码,如果以后还有其他地方需要发送验证码可以区分一下。之前写的redis的操作类没有这种类型的存储方法,自己创建一个
接着要想想它的逻辑,
第一当然是获取redis链接。。。。。
2、当获取redis链接成功后,先判断传过来的ip是否为null,为null返回3,不为null,接着判断缓存中key2的存储的次数,如果大于10就返回3,如果都没问题,我们就开始存储key3,使用setnx,如果key不存在存储,存在(之前的验证码还没有过期)不存储。
3、接着获取redis中的key3并且判断是否超过次数,如果不超过就开始设置之前存的key1的过期时间为60秒,并且key3的值+1并且返回,如果key3是第一次存储(手机号第一次注册),我们就设置key3的过期时间为一天,并且如果key2也是第一次存储,我们也设置key2的过期时间为一天。
4、最后都没有报错,存储设置成功,返回0表示可以开始发送验证码短信。
/**
* Author ljs
* Description 缓存手机验证码专用 限制了发送次数
*
* @return 1 当前验证码未过期 2 手机号超过当前验证码次数上限 3、ip超过当日验证码上线
* Date 2018/10/2 15:57
**/
public int cacheForVerificationCode(String key, String verCode, String type, int second, String ip) throws MaMaBikeException {
try {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
/**ip次数判断**/
String ipKey = "ip." + ip;
if (ip == null) {
return 3;
} else {
String ipSendCount = jedis.get(ipKey);
try {
if (ipSendCount != null && Integer.parseInt(ipSendCount) >= 10) {
return 3;
}
} catch (NumberFormatException e) {
log.error("Fail to process ip send count", e);
return 3;
}
}
/**验证码存储,被设置了返回0**/
long succ = jedis.setnx(key, verCode);
if (succ == 0) {
return 1;
}
/**手机号次数判断**/
String phoneCountKey = key + "." + type;
String sendCount = jedis.get(phoneCountKey);
try {
if (sendCount != null && Integer.parseInt(sendCount) >= 10) {
jedis.del(phoneCountKey);
return 2;
}
} catch (NumberFormatException e) {
log.error("Fail to process send count", e);
jedis.del(key);
return 2;
}
/**都没问题之后给三个key设置过期时间并且value+1**/
try {
jedis.expire(key, second);
long val = jedis.incr(ipKey);
/**该ip是第一次存储**/
if (val == 1) {
jedis.expire(=ipKey, 86400);
}
val = jedis.incr(phoneCountKey);
if (val == 1) {
jedis.expire(phoneCountKey, 86400);
}
}catch (Exception e){
log.error("Fail to cache data into redis", e);
}
}
}
} catch (Exception e) {
log.error("Fail to cache for expiry", e);
throw new MaMaBikeException("Fail to cache for expiry");
}
return 0;
}
3、写好redis存储之后,接着只需要在service判断1,2,3就行了
/**2、存到redis**/
int result = redis.cacheForVerificationCode(VERIFYCODE_PREFIX+mobile,verCode,"reg",60,ip);
if(result==1){
log.error("当前验证码未过期,请稍后重试");
throw new MaMaBikeException("当前验证码未过期,请稍后重试");
}else if(result==2){
log.error("超过当日验证码次数上限");
throw new MaMaBikeException("超过当日验证码次数上限");
}else if(result==3){
log.error("超过当日验证码次数上限{}", ip);
throw new MaMaBikeException(ip + "超过当日验证码次数上限");
}
4、在发送之前,需要一个发送短信的工具类,为了以后如果更换其他平台的短信服务,我们可以弄个接口进行解耦。
5、接下来就开始调用发送短信的工具类了,但是由于发短信和我们本身的业务没有关系,而且它成不成功和我们后续逻辑也没有关系,抛异常再发一次的都是秒滴那边的事,我们也并不依赖它返回的结果,不管它发没发成功。所以我们不在service层直接调用工具类来发送短信,我们需要进行解耦,这时候就可以使用mq来把这个东西搞出去(异步),进行解耦。
6、整合mq
6.1、pom
<!--整合mq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
6.2、因为这也是个第三方组件,我们把mq配置写到dev.xml里,而不是主配置里
#activeMQ
activemq:
broker-url: tcp://localhost:61616
pool:
enabled: false
6.3、启动mq
6.4、
/**
* Author ljs
* Description mq处理器,既可以发送短信又可以监听发送短信的队列
* Date 2018/10/3 11:13
**/
@Component(value = "smsProcessor")
public class SmsProcessor {
@Autowired
private JmsMessagingTemplate jmsTemplate;
@Autowired
@Qualifier("verCodeService")
private SmsSender smsSender;
public void sendSmsToQueue(Destination destination, final String message){
jmsTemplate.convertAndSend(destination, message);
}
@JmsListener(destination="sms.queue")
public void doSendSmsMessage(String text){
JSONObject jsonObject = JSON.parseObject(text);
smsSender.sendSms(jsonObject.getString("mobile"),jsonObject.getString("tplId"),jsonObject.getString("vercode"));
}
}
service
//验证码推送到队列
Destination destination = new ActiveMQQueue(SMS_QUEUE);
Map<String,String> smsParam = new HashMap<>();
smsParam.put("mobile",mobile);
smsParam.put("tplId", Constants.MDSMS_VERCODE_TPLID);
smsParam.put("vercode",verCode);
String message = JSON.toJSONString(smsParam);
sms.sendSmsToQueue(destination,message);
记得在paramater里加上sendVercode,因为这是一个无需验证的url。之后debug,
body里把号码发过去就行了,最后手机也收到验证码。