首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 簡訊發送頻率限制實作:基於 ConcurrentMap 與定時清理的線程安全方案

     編輯:Java 相關知識

背景

防止「簡訊炸彈」攻擊,需限制同一手機號或 IP 的發送頻率。單純用 Session 易被繞過(重啟瀏覽器、清除 Cache),需採用全域資料結構。

核心設計

1. 實體與介面

public class SmsEntity {
    private Integer id;
    private String mobile;
    private String ip;
    private Integer type;
    private Date time;
    private String captcha;
    // getter/setter/建構子省略
}

public interface SmsFilter {
    void init() throws Exception;
    boolean filter(SmsEntity smsEntity); // true=允許發送
    void destroy();
}

2. 頻率過濾器實作

使用 ConcurrentHashMap<String, Long> 記錄「手機號/IP → 最後發送時間」。核心邏輯在 setSendTime:

public class FrequencyFilter implements SmsFilter {
    private long sendInterval; // 毫秒
    private ConcurrentMap sendAddressMap = new ConcurrentHashMap<>();

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

    private boolean setSendTime(String id) {
        long now = System.currentTimeMillis();
        Long last = sendAddressMap.putIfAbsent(id, now);
        if (last == null) return true; // 首次發送

        long nextAllowed = last + sendInterval;
        if (now < nextAllowed) return false; // 尚在冷卻期

        // 嘗試以 CAS 方式更新時間,防止併發競爭
        if (sendAddressMap.replace(id, last, now)) return true;
        // 替換失敗可能是被清理線程移除,嘗試重新放入
        return sendAddressMap.putIfAbsent(id, now) == null;
    }
}

3. 定時清理過期資料

避免 Map 無限增長導致 OOM,啟動 Timer 定期移除超過間隔的條目。

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);
}

private void cleanSendAddressMap() {
    long now = System.currentTimeMillis();
    long expireThreshold = now - sendInterval;
    for (String key : sendAddressMap.keySet()) {
        Long sendTime = sendAddressMap.get(key);
        if (sendTime != null && sendTime < expireThreshold) {
            sendAddressMap.remove(key, sendTime);
        }
    }
}

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

服務層整合

public class SmsService {
    private Sms sms;
    private List filters;

    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;
    }
    // ...
}

將 FrequencyFilter 等過濾器透過 DI 注入即可。

注意事項

  • 手機號與 IP 獨立限制,任一超頻即攔截。
  • CAS 更新配合清理線程,保證高併發下的正確性。
  • 生產環境建議改用 Redis 實現分散式限流。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved