任务需要:针对之前对接的短信服务接口,需要新增如下短信发送的规则内容。
短信发送规则:
规则一:针对相同号码的短信发送的间隔不到低于120秒。
规则二:限定客户端IP每天短信发送次数
规则三:限定手机号码每天短信发送次数
解决思路:
1、针对规则一 的解决思路:采用Redis存储相关key 值,并设定过期时间为120 秒,如果在120 秒内重复发送,抛出短信自定义异常类(SMSException),由系统全局捕获,并提示相关错误信息给前端。
2、针对规则二 的解决思路:采用Redis存储相关key 值,并设定过期时间为1天,如果在1天内重复发送,超出规定次数,抛出短信自定义异常类(SMSException),由系统全局捕获,并提示相关错误信息给前端。
3、针对规则三 的解决思路:采用Redis存储相关key 值,并设定过期时间为1天,如果在1天内指定手机号码重复发送,超出规定次数,抛出短信自定义异常类(SMSException),由系统全局捕获,并提示相关错误信息给前端。
4、针对多规则的条件判断,我们采用拦截链模式。
示例功能代码:
1、自定义短信拦截接口
package com.zzg.sms.filter;
import com.zzg.redis.util.RedisUtil;
import com.zzg.sms.exception.SMSException;
public abstract class SMSHandler {
private RedisUtil redisUtil;
public RedisUtil getRedisUtil() {
return redisUtil;
}
public void setRedisUtil(RedisUtil redisUtil) {
this.redisUtil = redisUtil;
}
public SMSHandler(RedisUtil redisUtil) {
super();
this.redisUtil = redisUtil;
}
public abstract void process(String phone, String ip) throws SMSException;
}
2、自定义短信拦截接口实现类
package com.zzg.sms.filter;
import com.zzg.redis.util.RedisUtil;
import com.zzg.sms.exception.SMSException;
/**
*
* @ClassName: SMSIntervalHandler
* @Description: 短信发送间隔拦截
*/
public class SMSIntervalHandler extends SMSHandler {
public static final String SMSINTERVAL = "SMSINTERVAL";
public SMSIntervalHandler(RedisUtil redisUtil) {
super(redisUtil);
// TODO Auto-generated constructor stub
}
@Override
public void process(String phone, String ip) {
// TODO Auto-generated method stub
String key = phone.concat("_").concat(SMSINTERVAL);
// 根据key获取已请求次数
Integer max = (Integer) getRedisUtil().get(key);
if (max == null) {
// set时一定要加过期时间
getRedisUtil().set(key, 1, 120);
} else {
throw new SMSException("短信发送过于频繁,请120秒后再重试");
}
}
}
package com.zzg.sms.filter;
import com.zzg.common.util.ApplicationPropertiesHolder;
import com.zzg.redis.util.RedisUtil;
import com.zzg.sms.exception.SMSException;
/**
*
* @ClassName: SMSIPHandler
* @Description: 服务IP短信发送拦截
*/
public class SMSIPHandler extends SMSHandler {
public static final String SMSTOTAL = "SMSTOTAL";
public SMSIPHandler(RedisUtil redisUtil) {
super(redisUtil);
// TODO Auto-generated constructor stub
}
@Override
public void process(String phone, String ip) {
// TODO Auto-generated method stub
String key = ip.concat("_").concat(SMSTOTAL);
//根据key获取已请求次数
Integer max = (Integer) getRedisUtil().get(key);
// 系统配置每个手机号码可以发送次数
Integer number = Integer.valueOf(ApplicationPropertiesHolder.getProperty("ip.number.day", "100"));
if(max == null){
//set时一定要加过期时间
getRedisUtil().setDay(key, 1, 1);
}else if(max < number){
getRedisUtil().setDay(key, max+1, 1);
}else{
throw new SMSException(String.format("客户端IP超出每天短信发送次数:%d", number));
}
}
}
package com.zzg.sms.filter;
import com.zzg.common.util.ApplicationPropertiesHolder;
import com.zzg.redis.util.RedisUtil;
import com.zzg.sms.exception.SMSException;
/**
*
* @ClassName: SMSNumberHandler
* @Description: 手机号码发送次数拦截
*/
public class SMSNumberHandler extends SMSHandler {
private static final String PHONENUMBER = "PHONENUMBER";
public SMSNumberHandler(RedisUtil redisUtil) {
super(redisUtil);
}
@Override
public void process(String phone, String ip) {
// 手机号码发送次数
String key = phone.concat("_").concat(PHONENUMBER);
//根据key获取已请求次数
Integer max = (Integer) getRedisUtil().get(key);
// 系统配置每个手机号码可以发送次数
Integer number = Integer.valueOf(ApplicationPropertiesHolder.getProperty("phone.number.day", "10"));
if(max == null){
//set时一定要加过期时间
getRedisUtil().setDay(key, 1, 1);
}else if(max < number){
getRedisUtil().setDay(key, max+1, 1);
}else{
throw new SMSException(String.format("此手机号码超出每天限制短信发送数量:%d,请明天再尝试发送", number));
}
}
}
3、短信拦截链实例类
package com.zzg.component;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.zzg.redis.util.RedisUtil;
import com.zzg.sms.filter.SMSHandler;
import com.zzg.sms.filter.SMSIPHandler;
import com.zzg.sms.filter.SMSIntervalHandler;
import com.zzg.sms.filter.SMSNumberHandler;
/**
*
* @ClassName: SMSFilterChain
* @Description: sms 过滤拦截链
*/
@Component
public class SMSFilterChain {
@Autowired
private RedisUtil redisUtil;
private List<SMSHandler> filters = new ArrayList<SMSHandler>();
@PostConstruct
public void init(){
filters.add(new SMSIntervalHandler(redisUtil));
filters.add(new SMSIPHandler(redisUtil));
filters.add(new SMSNumberHandler(redisUtil));
}
public void exect(String phone, String ip){
for (SMSHandler filter : filters) {
filter.process(phone, ip);
}
}
}
4、自定义异常类
package com.zzg.sms.exception;
public class SMSException extends RuntimeException {
/**
* @Fields serialVersionUID : TODO(用一句话描述这个变量表示什么)
*/
private static final long serialVersionUID = 1L;
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public SMSException(String message) {
super();
this.message = message;
}
}
5、全局异常捕获
package com.zzg.global;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.zzg.common.entity.Result;
import com.zzg.sms.exception.SMSException;
/**
*
* @ClassName: GlobalException
* @Description: 全局异常处理
*/
@ControllerAdvice
public class GlobalException {
// 日志记录
public static final Logger logger = LoggerFactory.getLogger(GlobalException.class);
@ExceptionHandler(value = SMSException.class)
@ResponseBody
public Result exceptionHandler(SMSException e) {
logger.error("error: {}", e.getMessage(), e);
return Result.error(e.getMessage());
}
}