Limit the frequency of java SMS series

  • 2020-05-05 11:15:38
  • OfStack

This is the second part of sending text messages, and here's how to limit the frequency of sending text messages to the same user (by phone number and ip).

1. Use session

If is web program, record the last time send in session also can, but can be around in the past. The most simple, direct and restart your browser, or clear cache can tag session data, you can bypass the records in the session. Although a lot of people are not computer professional, also didn't learned that. But we need to pay attention to is that send frequency are limited, is in order to prevent "SMS bomb", This is when someone is making malicious and frequent requests to send text messages to a cell phone number

Next, we use "global" data to limit the frequency of sending to the same user

2, define interface, entity class

The entity classes we need are as follows :

SmsEntity.java


public class SmsEntity{
  private Integer id;
  private String mobile;
  private String ip;
  private Integer type;
  private Date time;
  private String captcha;

  //  Omit the constructor and getter , setter methods 
}

The filter interface is as follows :

SmsFilter.java


public interface SmsFilter {

  /**
   *  Initializes the filter 
   */
  void init() throws Exception;

  /**
   *  Determine whether a text message can be sent .
   * @param smsEntity  Text message content to be sent 
   * @return  Can be sent back true,  Otherwise returns false
   */
  boolean filter(SmsEntity smsEntity);

  /**
   *  Destroy the filter 
   */
  void destroy();

}

3. Main code

To limit the frequency of sending, we need to record a certain mobile phone number (IP) and the last time of sending a message

FrequencyFilter.java


public class FrequencyFilter implements SmsFilter {
  /**
   *  Sending interval ,  unit :  ms 
   */
  private long sendInterval;
  private ConcurrentMap<String, Long> sendAddressMap = new ConcurrentHashMap<>();

  //  Some of the dead code has been omitted 

  @Override
  public boolean filter(SmsEntity smsEntity) {
    if(setSendTime(smsEntity.getMobile()) && setSendTime(smsEntity.getIp())){
      return true;
    }
    return false;
  }

  /**
   *  Change the send time to the current time .
   *  If the time interval from the last send is greater than {@link #sendInterval} Set the sending time to the current time .  Otherwise, nothing is changed .
   *
   * @param id  Send mobile phone number   or  ip
   * @return  If successful, change the send time to the current time ,  It returns true.  Otherwise returns false
   */
  private boolean setSendTime(String id) {
    long currentTime = System.currentTimeMillis();

    Long sendTime = sendAddressMap.putIfAbsent(id, currentTime);
    if(sendTime == null) {
      return true;
    }

    long nextCanSendTime = sendTime + sendInterval;
    if(currentTime < nextCanSendTime) {
      return false;
    }

    return sendAddressMap.replace(id, sendTime, currentTime);
  }
}

Here, the main logic is implemented in the setSendTime method :

Line 25-28 of : first, assume that the user is sending a text message for the first time, then put the current time in sendAddressMap

Lines 30-33 of : if the user is not sending a text message for the first time, then it is necessary to determine whether the time between the last message and the current time is less than the sending time interval

line 35: if the time interval is large enough, try setting the sending time to the current time.

can send a text message if the replacement succeeds

1) then you can repeat 25-35 lines to make sure it is absolutely correct 2) it can also be directly considered that it cannot be sent, because although theoretically the time of "executing 26-35 lines" may be greater than the "sending interval ", what is the probability? You can basically ignore it.
This code implements the frequency limit, but if there is only "in" and no "out" then sendAddressMap will take up more and more content until the OutOfMemoryError exception is generated

4. Clean up expired data

FrequencyFilter.java


/**
 *  On top of the above code ,  Add the following code 
 */
public class FrequencyFilter implements SmsFilter {
  private long cleanMapInterval;
  private Timer timer = new Timer("sms_frequency_filter_clear_data_thread");

  @Override
  public void init() {
    timer.schedule(new TimerTask() {
      @Override
      public void run() {
        cleanSendAddressMap();
      }
    }, cleanMapInterval, cleanMapInterval);
  }

  /**
   *  will sendAddressMap Delete all expired data in 
   */
  private void cleanSendAddressMap() {
    long currentTime = System.currentTimeMillis();
    long expireSendTime = currentTime - sendInterval;

    for(String key : sendAddressMap.keySet()) {
      Long sendTime = sendAddressMap.get(key);
      if(sendTime < expireSendTime) {
        sendAddressMap.remove(key, sendTime);
      }
    }
  }

  @Override
  public void destroy() {
    timer.cancel();
  }
}

This is not a complicated program. Start a timer and execute the cleanSendAddressMap method every cleanMapInterval millisecond to clean up stale data

The cleanSendAddressMap method first gets the current time and gets a time value based on the current time: all those who sent a message after this time cannot now send a message again

Of course, when the last line of sendAddressMap.replace (id, sendTime, currentTime) fails, it may not be replaced by another thread, or it may be deleted by the clean thread

FrequencyFilter.java


private boolean setSendTime(String id) {
  //  Omit the previous code 
  if(sendAddressMap.replace(id, sendTime, currentTime)) {
    return true;
  }
  return sendAddressMap.putIfAbsent(id, currentTime) == null;
}

here, if the substitution is successful, returns true.

If the replacement is not successful, then the other line may have been replaced first (the first case); It could also be deleted by the cleanup thread (in the second case); It can even be deleted by the cleanup thread before another thread inserts a new time value (in the third case).

if it is the first case or the third case, the situation and the analysis of the beginning, can think directly, cannot send. if it is the second case, you should can be sent. to confirm what kind of situation, we can perform a putIfAbsent, if successful, that is the second case, you can send; Otherwise in the first or third case, you can't send.

At this point, the code to limit the sending time is complete. Of course, the program also has a little bug or "feature ":

If a customer with IP "192.168.0.1" requests to send a text message to cell phone number "12345678900" and then requests to send a text message to cell phone number "12345678900" on a machine with sendInterval "192.168.0.2" within sendInterval, the message will not be sent and the last time of cell phone number "12345678900" will be set to the current time.

5. Use instance

Here we provide an Server layer to show how the code from the previous article and this one can be put together :

SmsService.java


public class SmsService{
  private Sms sms;
  private List<SmsFilter> filters;
  private Properties template;

  //  Part of the code has been omitted 

  /**
   *  Send verification code 
   *
   * @param smsEntity  Basic data for sending SMS messages 
   * @return  If the submission is successful ,  return 0.  Otherwise, other values are returned .
   */
  public int sendCaptcha(SmsEntity smsEntity){
    for(SmsFilter filter : filters) {
      if(!filter.filter(smsEntity)){
        return 1;
      }
    }
    if(SmsEntity.REGISTER_TYPE.equals(smsEntity.getType())) {
      sendRegisterSms(smsEntity);
    }
    else{
      return 2;
    }
    return 0;
  }

  /**
   *  Send the registration verification code 
   *
   * @param smsEntity  Basic data for sending SMS messages 
   */
  private void sendRegisterSms(SmsEntity smsEntity) {
    sms.sendMessage(smsEntity.getMobile(),
        template.getProperty("register").replace("{captcha}", smsEntity.getCaptcha()));
  }

}

Then "inject" FrequencyFilter and AsyncSmsImpl from the previous article with set method.

The above is the entire content of this article, I hope to help you learn java programming.


Related articles: