From 82174aa6c625bb93653a661cb954b165154bbcf2 Mon Sep 17 00:00:00 2001 From: heqijun Date: Wed, 11 Jun 2025 13:47:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E7=9F=AD=E4=BF=A1=E9=99=90=E6=B5=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cache/controller/CacheController.java | 38 ++++++-- .../common/clients/BeaconCacheClient.java | 29 ++++-- .../common/constant/CacheConstant.java | 6 ++ .../common/enums/ExceptionEnums.java | 2 + .../impl/LimitHourStrategyFilter.java | 90 +++++++++++++++++++ .../impl/LimitMinuteStrategyFilter.java | 36 +++++++- .../utils/StrategyCheckFailedUtil.java | 1 + 7 files changed, 187 insertions(+), 15 deletions(-) create mode 100644 beacon-strategy/src/main/java/com/mashibing/strategy/service/strategyfilter/impl/LimitHourStrategyFilter.java diff --git a/beacon-cache/src/main/java/com/mashibing/cache/controller/CacheController.java b/beacon-cache/src/main/java/com/mashibing/cache/controller/CacheController.java index e9f74ca..b6aa504 100644 --- a/beacon-cache/src/main/java/com/mashibing/cache/controller/CacheController.java +++ b/beacon-cache/src/main/java/com/mashibing/cache/controller/CacheController.java @@ -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("【缓存模块】set:key = {},\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("【缓存模块】setnx:key = {},\nvalue = {},time:{}", key, value, time); + return redisClient.setNx(key, value, time, TimeUnit.SECONDS); + } + @GetMapping("hget/{key}") public Map hget(@PathVariable String key) { log.info("【缓存模块】hget:key={}", 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... 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 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 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 key是key,value是value */ - @PostMapping("/pipeline/string") + @PostMapping("pipeline/string") public void pipelineString(@RequestBody Map map) { log.info("【缓存模块】pipelineString: 数量 = {},", map.size()); long start = System.currentTimeMillis(); diff --git a/beacon-common/src/main/java/com/mashibing/common/clients/BeaconCacheClient.java b/beacon-common/src/main/java/com/mashibing/common/clients/BeaconCacheClient.java index d8170a1..4cc7afd 100644 --- a/beacon-common/src/main/java/com/mashibing/common/clients/BeaconCacheClient.java +++ b/beacon-common/src/main/java/com/mashibing/common/clients/BeaconCacheClient.java @@ -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... value); + void sadd(@PathVariable String key, @RequestBody Map... 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 sinterStr(@PathVariable(value = "key") String key, @PathVariable String sinterkey, @RequestBody String... value); + Set 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 zRangeByScore(@PathVariable String key, @PathVariable Long start, @PathVariable Long end); + + @GetMapping("cache/zremove/{key}/{member}/") + long zRemove(@PathVariable String key, @PathVariable String member); /** * 大批量写入String数据 diff --git a/beacon-common/src/main/java/com/mashibing/common/constant/CacheConstant.java b/beacon-common/src/main/java/com/mashibing/common/constant/CacheConstant.java index 0cea5fb..2a0864e 100644 --- a/beacon-common/src/main/java/com/mashibing/common/constant/CacheConstant.java +++ b/beacon-common/src/main/java/com/mashibing/common/constant/CacheConstant.java @@ -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:"; } diff --git a/beacon-common/src/main/java/com/mashibing/common/enums/ExceptionEnums.java b/beacon-common/src/main/java/com/mashibing/common/enums/ExceptionEnums.java index 19e8fc8..c63d2df 100644 --- a/beacon-common/src/main/java/com/mashibing/common/enums/ExceptionEnums.java +++ b/beacon-common/src/main/java/com/mashibing/common/enums/ExceptionEnums.java @@ -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; diff --git a/beacon-strategy/src/main/java/com/mashibing/strategy/service/strategyfilter/impl/LimitHourStrategyFilter.java b/beacon-strategy/src/main/java/com/mashibing/strategy/service/strategyfilter/impl/LimitHourStrategyFilter.java new file mode 100644 index 0000000..4a1a250 --- /dev/null +++ b/beacon-strategy/src/main/java/com/mashibing/strategy/service/strategyfilter/impl/LimitHourStrategyFilter.java @@ -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 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("【策略模块-小时限流校验】小时限流校验通过!!!"); + } +} diff --git a/beacon-strategy/src/main/java/com/mashibing/strategy/service/strategyfilter/impl/LimitMinuteStrategyFilter.java b/beacon-strategy/src/main/java/com/mashibing/strategy/service/strategyfilter/impl/LimitMinuteStrategyFilter.java index 6bf5e62..2fc44b2 100644 --- a/beacon-strategy/src/main/java/com/mashibing/strategy/service/strategyfilter/impl/LimitMinuteStrategyFilter.java +++ b/beacon-strategy/src/main/java/com/mashibing/strategy/service/strategyfilter/impl/LimitMinuteStrategyFilter.java @@ -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); + } } } diff --git a/beacon-strategy/src/main/java/com/mashibing/strategy/utils/StrategyCheckFailedUtil.java b/beacon-strategy/src/main/java/com/mashibing/strategy/utils/StrategyCheckFailedUtil.java index 4745914..ee40e56 100644 --- a/beacon-strategy/src/main/java/com/mashibing/strategy/utils/StrategyCheckFailedUtil.java +++ b/beacon-strategy/src/main/java/com/mashibing/strategy/utils/StrategyCheckFailedUtil.java @@ -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); }