防止「簡訊炸彈」攻擊,需限制同一手機號或 IP 的發送頻率。單純用 Session 易被繞過(重啟瀏覽器、清除 Cache),需採用全域資料結構。
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();
}
使用 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;
}
}
避免 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 注入即可。