实现验证码短信限流功能

main
heqijun 3 months ago
parent f3c92a0c0f
commit 82174aa6c6

@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author heqijun
@ -30,12 +31,18 @@ public class CacheController {
return redisClient.get(key);
}
@GetMapping("set/{key}/{value}")
@PostMapping("set/{key}/{value}")
public void set(@PathVariable String key, @PathVariable String value) {
log.info("【缓存模块】setkey = {}\nvalue = {}", key, value);
redisClient.set(key, value);
}
@PostMapping("setnx/{key}/{value}/{time}")
public boolean setnx(@PathVariable String key, @PathVariable String value, @PathVariable Long time) {
log.info("【缓存模块】setnxkey = {}\nvalue = {},time:{}", key, value, time);
return redisClient.setNx(key, value, time, TimeUnit.SECONDS);
}
@GetMapping("hget/{key}")
public Map hget(@PathVariable String key) {
log.info("【缓存模块】hgetkey={}", key);
@ -64,19 +71,19 @@ public class CacheController {
redisClient.hSet(key, hash);
}
@PostMapping("/sadd/{key}")
@PostMapping("sadd/{key}")
public void sadd(@PathVariable(value = "key") String key, @RequestBody Map<String, Object>... value) {
log.info("【缓存模块】sadd: key = {}\nvalue = {}", key, value);
redisClient.sAdd(key, value);
}
@PostMapping("/saddstr/{key}")
@PostMapping("saddstr/{key}")
public void saddStr(@PathVariable(value = "key") String key, @RequestBody String... value) {
log.info("【缓存模块】saddStr: key = {}\nvalue = {}", key, value);
redisClient.sAdd(key, value);
}
@PostMapping("/sinterstr/{key}/{sinterkey}")
@PostMapping("sinterstr/{key}/{sinterkey}")
public Set<Object> sinterStr(@PathVariable(value = "key") String key, @PathVariable String sinterkey, @RequestBody String... value) {
log.info("【缓存模块】sinterStr: key = {}sinterkey={}\nvalue = {}", key, sinterkey, value);
//1. 存入key和value
@ -91,7 +98,7 @@ public class CacheController {
return result;
}
@PostMapping("/smember/{key}")
@PostMapping("smember/{key}")
public Set smember(@PathVariable(value = "key") String key) {
log.info("【缓存模块】smember: key = {}", key);
Set value = redisClient.sMembers(key);
@ -99,12 +106,31 @@ public class CacheController {
return value;
}
@PostMapping("zadd/{key}/{member}/{score}")
public boolean zAdd(@PathVariable String key, @PathVariable String member, @PathVariable Long score) {
log.info("【缓存模块】zadd: key = {}member = {},score = {}", key, member, score);
return redisClient.zAdd(key, member, score);
}
@GetMapping("zrangebyscore/{key}/{start}/{end}")
public Set<Object> zRangeByScore(@PathVariable String key, @PathVariable long start, @PathVariable long end) {
log.info("【缓存模块】zrange: key = {}start = {},end = {}", key, start, end);
return redisClient.zRangeByScore(key, start, end);
}
@GetMapping("zremove/{key}/{member}/")
public long zRemove(@PathVariable String key, @PathVariable String member) {
Long l = redisClient.zRemove(key, member);
log.info("【缓存模块】zadd: key = {}member = {},编号 = {}", key, member, l);
return l;
}
/**
* String
*
* @param map keykeyvaluevalue
*/
@PostMapping("/pipeline/string")
@PostMapping("pipeline/string")
public void pipelineString(@RequestBody Map<String, String> map) {
log.info("【缓存模块】pipelineString: 数量 = {}", map.size());
long start = System.currentTimeMillis();

@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author heqijun
@ -21,35 +22,47 @@ public interface BeaconCacheClient {
@GetMapping("cache/get/{key}")
String get(@PathVariable String key);
@GetMapping("cache/set/{key}/{value}")
@PostMapping("cache/set/{key}/{value}")
void set(@PathVariable String key, @PathVariable String value);
@PostMapping("cache/setnx/{key}/{value}/{time}")
boolean setnx(@PathVariable String key, @PathVariable String value, @PathVariable Long time);
@GetMapping("cache/hget/{key}")
Map hget(@PathVariable String key);
@GetMapping("cache/hget/{key}/{field}")
Object hget(@PathVariable(value = "key") String key, @PathVariable(value = "field") String field);
Object hget(@PathVariable String key, @PathVariable String field);
@GetMapping("cache/hgetString/{key}/{field}")
String hgetString(@PathVariable(value = "key") String key, @PathVariable(value = "field") String field);
String hgetString(@PathVariable String key, @PathVariable String field);
@GetMapping("cache/hget/{key}/{field}")
Integer hgetInteger(@PathVariable(value = "key") String key, @PathVariable(value = "field") String field);
Integer hgetInteger(@PathVariable String key, @PathVariable String field);
@PostMapping("cache/hset/{key}")
void hset(@PathVariable String key, @RequestBody Map hash);
@PostMapping("cache/sadd/{key}")
void sadd(@PathVariable(value = "key") String key, @RequestBody Map<String, Object>... value);
void sadd(@PathVariable String key, @RequestBody Map<String, Object>... value);
@PostMapping("cache/saddstr/{key}")
void saddStr(@PathVariable(value = "key") String key, @RequestBody String... value);
void saddStr(@PathVariable String key, @RequestBody String... value);
@PostMapping("cache/sinterstr/{key}/{sinterkey}")
Set<Object> sinterStr(@PathVariable(value = "key") String key, @PathVariable String sinterkey, @RequestBody String... value);
Set<Object> sinterStr(@PathVariable String key, @PathVariable String sinterkey, @RequestBody String... value);
@PostMapping("cache/smember/{key}")
Set smember(@PathVariable(value = "key") String key);
Set smember(@PathVariable String key);
@PostMapping("cache/zadd/{key}/{member}/{score}")
boolean zAdd(@PathVariable String key, @PathVariable String member, @PathVariable Long score);
@GetMapping("cache/zrangebyscore/{key}/{start}/{end}")
Set<Object> zRangeByScore(@PathVariable String key, @PathVariable Long start, @PathVariable Long end);
@GetMapping("cache/zremove/{key}/{member}/")
long zRemove(@PathVariable String key, @PathVariable String member);
/**
* String

@ -46,4 +46,10 @@ public class CacheConstant {
@Description("携号转网前缀")
public static final String TRANSFER = "transfer:";
@Description("分钟限流规则前缀")
public static final String LIMIT_MINUTE = "limit_minute:";
@Description("小时限流规则前缀")
public static final String LIMIT_HOUR = "limit_hour:";
}

@ -23,6 +23,8 @@ public enum ExceptionEnums {
HAVE_DIRTY_WORD(-13, "短信中包含敏感词信息!!"),
BLACK_GLOBAL(-14, "手机号是全局黑名单!!"),
BLACK_CLIENT(-15, "手机号是客户黑名单!!"),
LIMIT_MINUTE(-16, "达到分钟限流阈值!!"),
LIMIT_HOUR(-17, "达到小时限流阈值!!"),
;
private final int code;

@ -0,0 +1,90 @@
package com.mashibing.strategy.service.strategyfilter.impl;
import com.mashibing.common.constant.CacheConstant;
import com.mashibing.common.enums.ExceptionEnums;
import com.mashibing.common.exception.StrategyException;
import com.mashibing.common.pojo.StandardSubmit;
import com.mashibing.strategy.feignclient.CacheClient;
import com.mashibing.strategy.service.strategyfilter.StrategyFilter;
import com.mashibing.strategy.utils.StrategyCheckFailedUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Set;
import static java.time.ZoneOffset.UTC;
/**
* @author heqijun
* @ClassName: LimitHourStrategyFilter
* @Description: TODO()
* @date 2025/6/10 21:14
*/
@Slf4j
@Service(value = "limitHour")
public class LimitHourStrategyFilter implements StrategyFilter {
@Autowired
CacheClient cacheClient;
@Autowired
StrategyCheckFailedUtil strategyCheckFailedUtil;
private final long LIMIT_DURATION = 60 * 60 * 1000 - 1;
private final int LIMIT_COUNT = 3;
private static final String STRATEGY_NAME = "小时限流";
private static final int RETRY_MAX = 2;
@Override
public void strategy(StandardSubmit submit) {
if (submit.getState() != 0) {
return;
}
log.info("【策略模块-小时限流校验】开始====================================");
Long clientId = submit.getClientId();
String mobile = submit.getMobile();
long sendTimeMilli = submit.getSendTime().toInstant(ZoneOffset.of("+8")).toEpochMilli();
String key = CacheConstant.LIMIT_HOUR + clientId + CacheConstant.COLON + mobile;
boolean isSucceed = cacheClient.zAdd(key, String.valueOf(sendTimeMilli), sendTimeMilli);
//插入失败,开始重试
int retryCount = 0;
if (!isSucceed) {
while (retryCount < RETRY_MAX) {
retryCount++;
if (cacheClient.zAdd(key, String.valueOf(sendTimeMilli + retryCount), sendTimeMilli)) {
break;
}
}
//三次插入都失败,直接报错
if (retryCount == RETRY_MAX) {
log.error("【策略模块-小时限流校验】达到限流阈值,校验失败!!!");
strategyCheckFailedUtil.smsSendLog(submit, STRATEGY_NAME);
strategyCheckFailedUtil.smsPushReport(submit, STRATEGY_NAME);
throw new StrategyException(ExceptionEnums.LIMIT_HOUR);
}
}
//插入成功或者重试成功,开始滑动时间窗口检验
Set<Object> counts = cacheClient.zRangeByScore(key, sendTimeMilli - LIMIT_DURATION, sendTimeMilli);
if (counts != null && counts.size() > LIMIT_COUNT) {
log.error("【策略模块-小时限流校验】达到限流阈值,校验失败!!!");
//插入成功但发送失败,需要删除
cacheClient.zRemove(key, String.valueOf(sendTimeMilli + retryCount));
strategyCheckFailedUtil.smsSendLog(submit, STRATEGY_NAME);
strategyCheckFailedUtil.smsPushReport(submit, STRATEGY_NAME);
throw new StrategyException(ExceptionEnums.LIMIT_HOUR);
}
//插入成功,并且没有达到限流阈值
log.info("【策略模块-小时限流校验】小时限流校验通过!!!");
}
}

@ -1,12 +1,19 @@
package com.mashibing.strategy.service.strategyfilter.impl;
import com.mashibing.common.constant.CacheConstant;
import com.mashibing.common.enums.ExceptionEnums;
import com.mashibing.common.exception.StrategyException;
import com.mashibing.common.pojo.StandardSubmit;
import com.mashibing.strategy.feignclient.CacheClient;
import com.mashibing.strategy.service.strategyfilter.StrategyFilter;
import com.mashibing.strategy.utils.StrategyCheckFailedUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.HashSet;
/**
* @author heqijun
* @ClassName: LimitMinuteStrategyFilter
@ -21,8 +28,35 @@ public class LimitMinuteStrategyFilter implements StrategyFilter {
@Autowired
CacheClient cacheClient;
@Autowired
StrategyCheckFailedUtil strategyCheckFailedUtil;
private final String VALUE = "1";
private final long TIME_SECONDS = 60L;
private static final String STRATEGY_NAME = "分钟限流";
@Override
public void strategy(StandardSubmit submit) {
if (submit.getState() != 0) {
return;
}
//使用set nx达到控制效果
log.info("【策略模块-分钟限流校验】开始====================================");
Long clientId = submit.getClientId();
String mobile = submit.getMobile();
String key = CacheConstant.LIMIT_MINUTE + clientId + CacheConstant.COLON + mobile;
boolean isSucceed = cacheClient.setnx(key, VALUE, TIME_SECONDS);
//插入成功,校验通过
if (isSucceed) {
log.info("【策略模块-分钟限流校验】通过!!!");
} else {
//插入失败,报错
log.error("【策略模块-分钟限流校验】达到限流阈值,校验失败!!!");
strategyCheckFailedUtil.smsSendLog(submit, STRATEGY_NAME);
strategyCheckFailedUtil.smsPushReport(submit, STRATEGY_NAME);
throw new StrategyException(ExceptionEnums.LIMIT_MINUTE);
}
}
}

@ -57,6 +57,7 @@ public class StrategyCheckFailedUtil {
* @param strategyName
*/
public void smsSendLog(StandardSubmit submit, String strategyName) {
submit.setReportState(2);
rabbitTemplate.convertAndSend(RabbitMQConstant.SMS_WRITE_LOG, submit);
log.error("【策略模块-{}校验】发送写日志消息到{}队列,消息:{}", strategyName, RabbitMQConstant.SMS_WRITE_LOG, submit);
}

Loading…
Cancel
Save