增加小时/天 级别的限流

This commit is contained in:
2023-11-30 14:19:23 +08:00
parent a2740f4162
commit b443b8d007
6 changed files with 428 additions and 65 deletions

View File

@@ -14,8 +14,23 @@ public class Constants {
public static final String HTTP_HEADER_CLIENT_SIGN = "X-Client-Sign";
public static final String HTTP_HEADER_CLIENT_RANDOM = "X-Client-Random";
public static final String HTTP_HEADER_TS = "X-TS";
public static final String RATE_LIMIT_GLOBAL_SESSION_PREFIX = "global-session-rl-";
public static final String RATE_LIMIT_GLOBAL_USER_PREFIX = "global-user-rl-";
public static final String RATE_LIMIT_GLOBAL_SESSION_MIN_PREFIX = "global-session-min-rl-";
public static final String RATE_LIMIT_GLOBAL_ANONYMOUS_MIN_PREFIX = "global-anonymous-min-rl-";
public static final String RATE_LIMIT_GLOBAL_USER_MIN_PREFIX = "global-user-min-rl-";
public static final String RATE_LIMIT_GLOBAL_SESSION_HOUR_PREFIX = "global-session-hour-rl-";
public static final String RATE_LIMIT_GLOBAL_ANONYMOUS_HOUR_PREFIX = "global-anonymous-hour-rl-";
public static final String RATE_LIMIT_GLOBAL_USER_HOUR_PREFIX = "global-user-hour-rl-";
public static final String RATE_LIMIT_GLOBAL_SESSION_DAY_PREFIX = "global-session-day-rl-";
public static final String RATE_LIMIT_GLOBAL_ANONYMOUS_DAY_PREFIX = "global-anonymous-day-rl-";
public static final String RATE_LIMIT_GLOBAL_USER_DAY_PREFIX = "global-user-day-rl-";
public static final String CACHE_ADDRESS_TYPE = "addressType";
public static final String CACHE_CLIENT_RANDOM_PREFIX = "clientRandom::";
public static final String HTTP_HEADER_RESPONSE_TIME = "X-Response-Time";
@@ -29,4 +44,12 @@ public class Constants {
public static String SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_MIN = "session_global_rate_limit_per_min";
public static String SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_MIN = "user_global_rate_limit_per_min";
public static String SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_MIN = "anonymous_global_rate_limit_per_min";
public static String SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_HOUR = "session_global_rate_limit_per_hour";
public static String SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_HOUR = "user_global_rate_limit_per_hour";
public static String SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_HOUR = "anonymous_global_rate_limit_per_hour";
public static String SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_DAY = "session_global_rate_limit_per_day";
public static String SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_DAY = "user_global_rate_limit_per_day";
public static String SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_DAY = "anonymous_global_rate_limit_per_day";
}

View File

@@ -7,6 +7,7 @@ import cn.lihongjie.coal.common.RequestUtils;
import cn.lihongjie.coal.exception.BizException;
import cn.lihongjie.coal.ip.IpQueryService;
import cn.lihongjie.coal.loginUser.service.LoginUserService;
import cn.lihongjie.coal.ratelimit.RateLimiterService;
import cn.lihongjie.coal.sysconfig.service.SysConfigService;
import cn.lihongjie.coal.syslog.service.SysLogService;
@@ -22,9 +23,6 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.ContentType;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
@@ -46,6 +44,8 @@ public class RateLimitFilter extends OncePerRequestFilter {
@Autowired IpQueryService ipQueryService;
@Autowired LoginUserService loginUserService;
@Autowired RateLimiterService rateLimiterService;
@Override
public void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
@@ -63,55 +63,46 @@ public class RateLimitFilter extends OncePerRequestFilter {
if (StringUtils.isNotEmpty(sessionId)) {
RRateLimiter sessionRL =
redissonClient.getRateLimiter(
Constants.RATE_LIMIT_GLOBAL_SESSION_PREFIX + sessionId);
boolean acquire = sessionRL.tryAcquire(1);
if (!acquire) {
sysLogService.saveSysLog(request, "限流模块", "会话限流", "");
RateLimiterService.RateLimitAcquireResult acquireSessionRL = rateLimiterService.acquireSessionRL(sessionId);
if (acquireSessionRL != RateLimiterService.RateLimitAcquireResult.SUCCESS ) {
sysLogService.saveSysLog(request, "限流模块", "会话限流", acquireSessionRL.name());
writeResponse(new BizException("当前会话请求被限流,请稍后再试"), response);
log.warn(
"会话限流: sessionId {} user {} request {}",
"会话限流: sessionId {} user {} request {} type {}",
sessionId,
Ctx.currentUser().getUsername(),
acquireSessionRL.name(),
request.getRequestURI());
return;
}
RRateLimiter userRL =
redissonClient.getRateLimiter(
Constants.RATE_LIMIT_GLOBAL_USER_PREFIX + Ctx.getUserId());
boolean acquire2 = userRL.tryAcquire(1);
if (!acquire2) {
sysLogService.saveSysLog(request, "限流模块", "用户限流", "");
writeResponse(new BizException("当前用户请求被限流,请稍后再试"), response);
RateLimiterService.RateLimitAcquireResult acquireUserRL = rateLimiterService.acquireUserRL(Ctx.currentUser().getId());
if (acquireUserRL != RateLimiterService.RateLimitAcquireResult.SUCCESS ) {
sysLogService.saveSysLog(request, "限流模块", "用户限流", acquireSessionRL.name());
writeResponse(new BizException("当前会话请求被限流,请稍后再试"), response);
log.warn(
"用户限流: sessionId {} user {} request {}",
"用户限流: sessionId {} user {} request {} type {}",
sessionId,
Ctx.currentUser().getUsername(),
acquireSessionRL.name(),
request.getRequestURI());
return;
}
} else {
RRateLimiter rateLimiter = redissonClient.getRateLimiter("global-iprl-" + ip);
rateLimiter.trySetRate(
RateType.OVERALL,
Integer.parseInt(
sysConfigService.getConfigVal(
Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_MIN)),
1,
RateIntervalUnit.MINUTES);
boolean acquire = rateLimiter.tryAcquire(1);
if (!acquire) {
sysLogService.saveSysLog(request, "限流模块", "IP限流", "");
rateLimiterService.initAnonymousIpRL(ip);
RateLimiterService.RateLimitAcquireResult acquire = rateLimiterService.acquireIpRL(ip);
if (acquire != RateLimiterService.RateLimitAcquireResult.SUCCESS) {
sysLogService.saveSysLog(request, "限流模块", "IP限流", acquire.name());
writeResponse(new BizException("请求被限流,请稍后再试"), response);
log.warn("ip {} request {} is rate limited", ip, request.getRequestURI());
log.warn("ip {} request {} type {} is rate limited", ip, request.getRequestURI(), acquire.name());
return;
}
}

View File

@@ -12,6 +12,7 @@ import cn.lihongjie.coal.loginUser.entity.LoginUserEntity;
import cn.lihongjie.coal.loginUser.mapper.LoginUserMapper;
import cn.lihongjie.coal.loginUser.repository.LoginUserRepository;
import cn.lihongjie.coal.loginUserHis.service.LoginUserHisService;
import cn.lihongjie.coal.ratelimit.RateLimiterService;
import cn.lihongjie.coal.session.SessionService;
import cn.lihongjie.coal.sysconfig.service.SysConfigService;
import cn.lihongjie.coal.user.service.UserService;
@@ -27,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@@ -45,8 +47,7 @@ import java.util.stream.Collectors;
@Service
@Slf4j
@Transactional
public
class LoginUserService extends BaseService<LoginUserEntity, LoginUserRepository> {
public class LoginUserService extends BaseService<LoginUserEntity, LoginUserRepository> {
public static final String CACHE_LOGIN_USER_BY_ID = "loginUserById";
@Autowired SysConfigService sysConfigService;
@Autowired ApplicationContext applicationContext;
@@ -207,22 +208,27 @@ class LoginUserService extends BaseService<LoginUserEntity, LoginUserRepository>
return dto;
}
@Autowired ApplicationEventPublisher applicationEventPublisher;
@Autowired SessionService sessionService;
@Autowired UserService userService;
@Autowired RateLimiterService rateLimiterService;
public void deleteLogin(String sessionId) {
try{
try {
LoginUserEntity loginUser = this.get(sessionId);
this.repository.deleteById(sessionId);
}catch (Exception e){
rateLimiterService.destroyRL(sessionId, loginUser.getUser().getId());
this.repository.deleteById(sessionId);
} catch (Exception e) {
}
this.clearCache(sessionId);
}
@Autowired SessionService sessionService;
@Autowired UserService userService;
public void logout(IdRequest request) {
String sessionId = request.getId();
if (sessionId != null) {
@@ -237,7 +243,6 @@ class LoginUserService extends BaseService<LoginUserEntity, LoginUserRepository>
}
}
public void adminLogout(IdRequest request) {
logout(request);

View File

@@ -0,0 +1,305 @@
package cn.lihongjie.coal.ratelimit;
import static cn.lihongjie.coal.common.Constants.*;
import cn.lihongjie.coal.common.Constants;
import cn.lihongjie.coal.sysconfig.service.SysConfigService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class RateLimiterService {
@Autowired RedissonClient redissonClient;
@Autowired SysConfigService sysConfigService;
public void initAnonymousIpRL(String ip) {
if (StringUtils.isEmpty(ip)) {
throw new RuntimeException("ip is empty");
}
RRateLimiter rateLimiter =
redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_MIN_PREFIX + ip);
rateLimiter.trySetRate(
RateType.OVERALL,
Integer.parseInt(
sysConfigService.getConfigVal(
Constants.SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_MIN)),
1,
RateIntervalUnit.MINUTES);
rateLimiter =
redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_HOUR_PREFIX + ip);
rateLimiter.trySetRate(
RateType.OVERALL,
Integer.parseInt(
sysConfigService.getConfigVal(
SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_HOUR)),
1,
RateIntervalUnit.HOURS);
rateLimiter =
redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_DAY_PREFIX + ip);
rateLimiter.trySetRate(
RateType.OVERALL,
Integer.parseInt(
sysConfigService.getConfigVal(
SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_DAY)),
1,
RateIntervalUnit.DAYS);
}
public RateLimitAcquireResult acquireIpRL(String ip) {
if (StringUtils.isEmpty(ip)) {
throw new RuntimeException("ip is empty");
}
RRateLimiter rateLimiter =
redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_MIN_PREFIX + ip);
if (!rateLimiter.tryAcquire(1)) {
return RateLimitAcquireResult.MIN;
}
rateLimiter =
redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_HOUR_PREFIX + ip);
if (!rateLimiter.tryAcquire(1)) {
return RateLimitAcquireResult.HOUR;
}
rateLimiter =
redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_ANONYMOUS_DAY_PREFIX + ip);
if (!rateLimiter.tryAcquire(1)) {
return RateLimitAcquireResult.DAY;
}
return RateLimitAcquireResult.SUCCESS;
}
public RateLimitAcquireResult acquireSessionRL(String session) {
if (StringUtils.isEmpty(session)) {
throw new RuntimeException("session is empty");
}
RRateLimiter rateLimiter =
redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_SESSION_MIN_PREFIX + session);
if (!rateLimiter.tryAcquire(1)) {
return RateLimitAcquireResult.MIN;
}
rateLimiter =
redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_SESSION_HOUR_PREFIX + session);
if (!rateLimiter.tryAcquire(1)) {
return RateLimitAcquireResult.HOUR;
}
rateLimiter =
redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_SESSION_DAY_PREFIX + session);
if (!rateLimiter.tryAcquire(1)) {
return RateLimitAcquireResult.DAY;
}
return RateLimitAcquireResult.SUCCESS;
}
public RateLimitAcquireResult acquireUserRL(String userId) {
if (StringUtils.isEmpty(userId)) {
throw new RuntimeException("userId is empty");
}
RRateLimiter rateLimiter =
redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_USER_MIN_PREFIX + userId);
if (!rateLimiter.tryAcquire(1)) {
return RateLimitAcquireResult.MIN;
}
rateLimiter =
redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_USER_HOUR_PREFIX + userId);
if (!rateLimiter.tryAcquire(1)) {
return RateLimitAcquireResult.HOUR;
}
rateLimiter =
redissonClient.getRateLimiter(RATE_LIMIT_GLOBAL_USER_DAY_PREFIX + userId);
if (!rateLimiter.tryAcquire(1)) {
return RateLimitAcquireResult.DAY;
}
return RateLimitAcquireResult.SUCCESS;
}
public void initRL(String sessionId, String userId) {
if (StringUtils.isEmpty(sessionId)) {
throw new RuntimeException("sessionId is empty");
}
if (StringUtils.isEmpty(userId)) {
throw new RuntimeException("userId is empty");
}
// 初始化限流
RRateLimiter sessionMinRL =
redissonClient.getRateLimiter(
Constants.RATE_LIMIT_GLOBAL_SESSION_MIN_PREFIX + sessionId);
sessionMinRL.trySetRate(
RateType.OVERALL,
Integer.parseInt(
sysConfigService.getConfigVal(
Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_MIN)),
1,
RateIntervalUnit.MINUTES);
RRateLimiter sessionHourRL =
redissonClient.getRateLimiter(
Constants.RATE_LIMIT_GLOBAL_SESSION_HOUR_PREFIX + sessionId);
sessionHourRL.trySetRate(
RateType.OVERALL,
Integer.parseInt(
sysConfigService.getConfigVal(
Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_HOUR)),
1,
RateIntervalUnit.HOURS);
RRateLimiter sessionDayRL =
redissonClient.getRateLimiter(
Constants.RATE_LIMIT_GLOBAL_SESSION_DAY_PREFIX + sessionId);
sessionDayRL.trySetRate(
RateType.OVERALL,
Integer.parseInt(
sysConfigService.getConfigVal(
Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_DAY)),
1,
RateIntervalUnit.DAYS);
RRateLimiter userMinRL =
redissonClient.getRateLimiter(Constants.RATE_LIMIT_GLOBAL_USER_MIN_PREFIX + userId);
userMinRL.trySetRate(
RateType.OVERALL,
Integer.parseInt(
sysConfigService.getConfigVal(
Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_MIN)),
1,
RateIntervalUnit.MINUTES);
RRateLimiter userHourRL =
redissonClient.getRateLimiter(
Constants.RATE_LIMIT_GLOBAL_USER_HOUR_PREFIX + userId);
userHourRL.trySetRate(
RateType.OVERALL,
Integer.parseInt(
sysConfigService.getConfigVal(
Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_HOUR)),
1,
RateIntervalUnit.HOURS);
RRateLimiter userDayRL =
redissonClient.getRateLimiter(Constants.RATE_LIMIT_GLOBAL_USER_DAY_PREFIX + userId);
userDayRL.trySetRate(
RateType.OVERALL,
Integer.parseInt(
sysConfigService.getConfigVal(
Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_DAY)),
1,
RateIntervalUnit.DAYS);
}
public void destroyRL(String sessionId, String userId) {
if (StringUtils.isEmpty(sessionId)) {
throw new RuntimeException("sessionId is empty");
}
if (StringUtils.isEmpty(userId)) {
throw new RuntimeException("userId is empty");
}
try {
RRateLimiter sessionMinRL =
redissonClient.getRateLimiter(
Constants.RATE_LIMIT_GLOBAL_SESSION_MIN_PREFIX + sessionId);
sessionMinRL.delete();
} catch (Exception e) {
log.warn("", e);
}
try {
RRateLimiter sessionHourRL =
redissonClient.getRateLimiter(
Constants.RATE_LIMIT_GLOBAL_SESSION_HOUR_PREFIX + sessionId);
sessionHourRL.delete();
} catch (Exception e) {
log.warn("", e);
}
try {
RRateLimiter sessionDayRL =
redissonClient.getRateLimiter(
Constants.RATE_LIMIT_GLOBAL_SESSION_DAY_PREFIX + sessionId);
sessionDayRL.delete();
} catch (Exception e) {
log.warn("", e);
}
try {
RRateLimiter userMinRL =
redissonClient.getRateLimiter(
Constants.RATE_LIMIT_GLOBAL_USER_MIN_PREFIX + userId);
userMinRL.delete();
} catch (Exception e) {
log.warn("", e);
}
try {
RRateLimiter userHourRL =
redissonClient.getRateLimiter(
Constants.RATE_LIMIT_GLOBAL_USER_HOUR_PREFIX + userId);
userHourRL.delete();
} catch (Exception e) {
log.warn("", e);
}
try {
RRateLimiter userDayRL =
redissonClient.getRateLimiter(
Constants.RATE_LIMIT_GLOBAL_USER_DAY_PREFIX + userId);
userDayRL.delete();
} catch (Exception e) {
log.warn("", e);
}
}
public enum RateLimitAcquireResult {
SUCCESS,
MIN, HOUR, DAY,
}
}

View File

@@ -12,6 +12,7 @@ import cn.lihongjie.coal.loginUserHis.entity.LoginUserHisEntity;
import cn.lihongjie.coal.loginUserHis.service.LoginUserHisService;
import cn.lihongjie.coal.organization.entity.OrganizationEntity;
import cn.lihongjie.coal.organization.service.OrganizationService;
import cn.lihongjie.coal.ratelimit.RateLimiterService;
import cn.lihongjie.coal.sysconfig.service.SysConfigService;
import cn.lihongjie.coal.user.dto.UserDto;
import cn.lihongjie.coal.user.entity.UserEntity;
@@ -28,9 +29,6 @@ import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
@@ -97,6 +95,8 @@ public class SessionService {
@Autowired RedissonClient redissonClient;
@Autowired RateLimiterService rateLimiterService;
@SneakyThrows
public void login(LoginDto dto) {
HttpServletRequest request =
@@ -182,29 +182,11 @@ public class SessionService {
his.setSessionId(sessionId);
// 初始化限流
RRateLimiter sessionRL = redissonClient.getRateLimiter(Constants.RATE_LIMIT_GLOBAL_SESSION_PREFIX + sessionId);
sessionRL.trySetRate(
RateType.OVERALL,
Integer.parseInt(
sysConfigService.getConfigVal(
Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_MIN)),
1,
RateIntervalUnit.MINUTES);
RRateLimiter userRL = redissonClient.getRateLimiter(Constants.RATE_LIMIT_GLOBAL_USER_PREFIX + user.getId());
userRL.trySetRate(
RateType.OVERALL,
Integer.parseInt(
sysConfigService.getConfigVal(
Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_MIN)),
1,
RateIntervalUnit.MINUTES);
loginUserHisService.save(his);
rateLimiterService.initRL(sessionId, user.getId());
} catch (Exception e) {

View File

@@ -117,6 +117,63 @@ public class SysConfigService extends BaseService<SysConfigEntity, SysConfigRepo
addNumberConfig(
all,
Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_HOUR,
"登录会话全局限流(每小时)",
3600 + "",
1L,
Integer.MAX_VALUE);
addNumberConfig(
all,
Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_HOUR,
"用户全局限流(每小时)",
3600 + "",
1L,
Integer.MAX_VALUE);
addNumberConfig(
all,
Constants.SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_HOUR,
"匿名访问全局限流(每小时)",
3600 + "",
1L,
Integer.MAX_VALUE);
addNumberConfig(
all,
Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_DAY,
"登录会话全局限流(每天)",
3600 * 8 + "",
1L,
Integer.MAX_VALUE);
addNumberConfig(
all,
Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_DAY,
"用户全局限流(每天)",
3600 * 8 + "",
1L,
Integer.MAX_VALUE);
addNumberConfig(
all,
Constants.SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_DAY,
"匿名访问全局限流(每天)",
3600 * 8 + "",
1L,
Integer.MAX_VALUE);
}
private void addNumberConfig(