refactor: 删除限流Filter 实体统一 文件归位
This commit is contained in:
@@ -120,6 +120,7 @@ CREATE TABLE `sys_menu` (
|
||||
`route_name` varchar(255) COMMENT '路由名称(Vue Router 中用于命名路由)',
|
||||
`route_path` varchar(128) COMMENT '路由路径(Vue Router 中定义的 URL 路径)',
|
||||
`component` varchar(128) COMMENT '组件路径(组件页面完整路径,相对于 src/views/,缺省后缀 .vue)',
|
||||
`external_url` varchar(512) COMMENT '外链地址',
|
||||
`perm` varchar(128) COMMENT '【按钮】权限标识',
|
||||
`always_show` tinyint DEFAULT 0 COMMENT '【目录】只有一个子路由是否始终显示(1-是 0-否)',
|
||||
`keep_alive` tinyint DEFAULT 0 COMMENT '【菜单】是否开启页面缓存(1-是 0-否)',
|
||||
|
||||
@@ -12,7 +12,7 @@ public interface RedisConstants {
|
||||
* 限流相关键
|
||||
*/
|
||||
interface RateLimiter {
|
||||
String IP = "rate_limiter:ip:{}"; // IP限流(示例:rate_limiter:ip:192.168.1.1)
|
||||
String API = "{}rate_limit:{}:{}"; // 接口限流
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,9 +24,5 @@ public interface SystemConstants {
|
||||
String ROOT_ROLE_CODE = "ROOT";
|
||||
|
||||
|
||||
/**
|
||||
* 系统配置 IP的QPS限流的KEY
|
||||
*/
|
||||
String SYSTEM_CONFIG_IP_QPS_LIMIT_KEY = "IP_QPS_THRESHOLD_LIMIT";
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.baomidou.mybatisplus.core.config.GlobalConfig;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.youlai.boot.framework.mybatis.handler.MyMetaObjectHandler;
|
||||
import com.youlai.boot.framework.mybatis.handler.AutoFillMetaObjectHandler;
|
||||
import com.youlai.boot.framework.mybatis.interceptor.MyDataPermissionHandler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -43,7 +43,7 @@ public class MybatisConfig {
|
||||
@Bean
|
||||
public GlobalConfig globalConfig() {
|
||||
GlobalConfig globalConfig = new GlobalConfig();
|
||||
globalConfig.setMetaObjectHandler(new MyMetaObjectHandler());
|
||||
globalConfig.setMetaObjectHandler(new AutoFillMetaObjectHandler());
|
||||
return globalConfig;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.time.LocalDateTime;
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MyMetaObjectHandler implements MetaObjectHandler {
|
||||
public class AutoFillMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
/**
|
||||
* 新增填充创建时间、更新时间
|
||||
@@ -15,7 +15,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class OnlineUserCountJob {
|
||||
public class OnlineUserCountTask {
|
||||
|
||||
private final SseSessionRegistry sessionRegistry;
|
||||
private final SseService sseService;
|
||||
@@ -5,28 +5,22 @@ import com.youlai.boot.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 菜单类型枚举(char)
|
||||
* 菜单类型枚举
|
||||
*
|
||||
* C:目录
|
||||
* M:菜单
|
||||
* B:按钮
|
||||
* @author Ray.Hao
|
||||
* @since 0.0.1
|
||||
*/
|
||||
@Getter
|
||||
public enum MenuTypeEnum implements IBaseEnum<String> {
|
||||
|
||||
CATALOG("C", "目录"),
|
||||
MENU("M", "菜单"),
|
||||
EXTERNAL("E", "外链"),
|
||||
BUTTON("B", "按钮");
|
||||
|
||||
/**
|
||||
* 数据库存储值
|
||||
*/
|
||||
@EnumValue
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* 友好名称
|
||||
*/
|
||||
private final String label;
|
||||
|
||||
MenuTypeEnum(String value, String label) {
|
||||
|
||||
@@ -2,10 +2,10 @@ package com.youlai.boot.system.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import com.youlai.boot.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -15,15 +15,9 @@ import java.util.Map;
|
||||
* @since 2023/3/6
|
||||
*/
|
||||
@TableName(value = "sys_menu", autoResultMap = true)
|
||||
@Getter
|
||||
@Setter
|
||||
public class Menu {
|
||||
/**
|
||||
* 菜单ID
|
||||
*/
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Menu extends BaseEntity {
|
||||
/**
|
||||
* 父菜单ID
|
||||
*/
|
||||
@@ -54,6 +48,11 @@ public class Menu {
|
||||
*/
|
||||
private String component;
|
||||
|
||||
/**
|
||||
* 外链地址
|
||||
*/
|
||||
private String externalUrl;
|
||||
|
||||
/**
|
||||
* 权限标识
|
||||
*/
|
||||
@@ -100,14 +99,4 @@ public class Menu {
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS, typeHandler = JacksonTypeHandler.class)
|
||||
private Map<String, Object> params;
|
||||
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ public class MenuForm {
|
||||
@Schema(description = "组件路径(vue页面完整路径,省略.vue后缀)")
|
||||
private String component;
|
||||
|
||||
@Schema(description = "外链地址")
|
||||
private String externalUrl;
|
||||
|
||||
@Schema(description = "权限标识")
|
||||
private String perm;
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ public class MenuVO {
|
||||
@Schema(description = "组件路径")
|
||||
private String component;
|
||||
|
||||
@Schema(description = "外链地址")
|
||||
private String externalUrl;
|
||||
|
||||
@Schema(description = "菜单排序(数字越小排名越靠前)")
|
||||
private Integer sort;
|
||||
|
||||
|
||||
@@ -56,6 +56,9 @@ public class RouteVO {
|
||||
|
||||
@Schema(description = "路由参数")
|
||||
private Map<String,String> params;
|
||||
|
||||
@Schema(description = "外链地址")
|
||||
private String externalUrl;
|
||||
}
|
||||
|
||||
@Schema(description = "子路由列表")
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.youlai.boot.system.security.config;
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import com.youlai.boot.framework.captcha.service.CaptchaService;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import com.youlai.boot.system.security.filter.RateLimiterFilter;
|
||||
import com.youlai.boot.framework.security.filter.CaptchaValidationFilter;
|
||||
import com.youlai.boot.framework.security.filter.TokenAuthenticationFilter;
|
||||
import com.youlai.boot.framework.security.handler.MyAccessDeniedHandler;
|
||||
@@ -86,8 +85,6 @@ public class SecurityConfig {
|
||||
.httpBasic(AbstractHttpConfigurer::disable) // 禁用 HTTP Basic 认证,避免弹窗式登录
|
||||
// 禁用 X-Frame-Options 响应头,允许页面被嵌套到 iframe 中
|
||||
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
||||
// 限流过滤器
|
||||
.addFilterBefore(new RateLimiterFilter(redisTemplate, configService), UsernamePasswordAuthenticationFilter.class)
|
||||
// 验证码校验过滤器
|
||||
.addFilterBefore(new CaptchaValidationFilter(captchaService), UsernamePasswordAuthenticationFilter.class)
|
||||
// 验证和解析过滤器
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
package com.youlai.boot.system.security.filter;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.common.constant.SystemConstants;
|
||||
import com.youlai.boot.common.result.ResultCode;
|
||||
import com.youlai.boot.common.util.IPUtils;
|
||||
import com.youlai.boot.common.result.ResponseWriter;
|
||||
import com.youlai.boot.system.service.ConfigService;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* IP 限流过滤器
|
||||
*
|
||||
* @author Theo
|
||||
* @since 2024/08/10 14:38
|
||||
*/
|
||||
@Slf4j
|
||||
public class RateLimiterFilter extends OncePerRequestFilter {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final ConfigService configService;
|
||||
|
||||
private static final long DEFAULT_IP_LIMIT = 10L; // 默认 IP 限流阈值
|
||||
|
||||
public RateLimiterFilter(RedisTemplate<String, Object> redisTemplate, ConfigService configService) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
this.configService = configService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 IP 是否触发限流
|
||||
* 默认限制同一 IP 每秒最多请求 10 次,可通过系统配置调整。
|
||||
* 如果系统未配置限流阈值,默认跳过限流。
|
||||
*
|
||||
* @param ip IP 地址
|
||||
* @return 是否限流:true 表示限流;false 表示未限流
|
||||
*/
|
||||
public boolean rateLimit(String ip) {
|
||||
// 限流 Redis 键
|
||||
String key = StrUtil.format(RedisConstants.RateLimiter.IP, ip);
|
||||
|
||||
// 自增请求计数
|
||||
Long count = redisTemplate.opsForValue().increment(key);
|
||||
if (count == null || count == 1) {
|
||||
// 第一次访问时设置过期时间为 1 秒
|
||||
redisTemplate.expire(key, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// 获取系统配置的限流阈值
|
||||
Object systemConfig = configService.getSystemConfig(SystemConstants.SYSTEM_CONFIG_IP_QPS_LIMIT_KEY);
|
||||
if (systemConfig == null) {
|
||||
// 系统未配置限流,跳过限流逻辑
|
||||
log.warn("系统未配置限流阈值,跳过限流");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 转换系统配置为限流值,默认为 10
|
||||
long limit = Convert.toLong(systemConfig, DEFAULT_IP_LIMIT);
|
||||
return count != null && count > limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 IP 限流逻辑
|
||||
* 如果 IP 请求超出限制,直接返回限流响应;否则继续执行过滤器链。
|
||||
*
|
||||
* @param request 请求体
|
||||
* @param response 响应体
|
||||
* @param filterChain 过滤器链
|
||||
*/
|
||||
@Override
|
||||
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response,
|
||||
@NotNull FilterChain filterChain) throws ServletException, IOException {
|
||||
// 获取请求的 IP 地址
|
||||
String ip = IPUtils.getIpAddr(request);
|
||||
|
||||
// 判断是否限流
|
||||
if (rateLimit(ip)) {
|
||||
// 返回限流错误信息
|
||||
ResponseWriter.writeError(response, ResultCode.REQUEST_CONCURRENCY_LIMIT_EXCEEDED);
|
||||
return;
|
||||
}
|
||||
|
||||
// 未触发限流,继续执行过滤器链
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -58,11 +58,4 @@ public interface ConfigService extends IService<Config> {
|
||||
*/
|
||||
boolean refreshCache();
|
||||
|
||||
/**
|
||||
* 获取系统配置
|
||||
* @param key 配置键
|
||||
* @return 配置值
|
||||
*/
|
||||
Object getSystemConfig(String key);
|
||||
|
||||
}
|
||||
|
||||
@@ -142,18 +142,4 @@ public class ConfigServiceImpl extends ServiceImpl<ConfigMapper, Config> impleme
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统配置
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return 配置值
|
||||
*/
|
||||
@Override
|
||||
public Object getSystemConfig(String key) {
|
||||
if (StringUtils.isNotBlank(key)) {
|
||||
return redisTemplate.opsForHash().get(RedisConstants.System.CONFIG, key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
t1.route_name,
|
||||
t1.route_path,
|
||||
t1.component,
|
||||
t1.external_url,
|
||||
t1.icon,
|
||||
t1.sort,
|
||||
t1.visible,
|
||||
|
||||
Reference in New Issue
Block a user