任务需要:针对之前对接的短信服务接口,需要新增如下短信发送的规则内容。

短信发送规则:

规则一:针对相同号码的短信发送的间隔不到低于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());
	}
}