refactor: 删除限流Filter 实体统一 文件归位

This commit is contained in:
Ray.Hao
2026-06-24 08:12:01 +08:00
parent b2dcd507a1
commit 9ee37ff42c
16 changed files with 31 additions and 163 deletions

View File

@@ -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-否)',

View File

@@ -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:{}:{}"; // 接口限流
}
/**

View File

@@ -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";
}

View File

@@ -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;
}

View File

@@ -18,7 +18,7 @@ import java.time.LocalDateTime;
*/
@Component
@RequiredArgsConstructor
public class MyMetaObjectHandler implements MetaObjectHandler {
public class AutoFillMetaObjectHandler implements MetaObjectHandler {
/**
* 新增填充创建时间、更新时间

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -38,6 +38,9 @@ public class MenuForm {
@Schema(description = "组件路径(vue页面完整路径,省略.vue后缀)")
private String component;
@Schema(description = "外链地址")
private String externalUrl;
@Schema(description = "权限标识")
private String perm;

View File

@@ -31,6 +31,9 @@ public class MenuVO {
@Schema(description = "组件路径")
private String component;
@Schema(description = "外链地址")
private String externalUrl;
@Schema(description = "菜单排序(数字越小排名越靠前)")
private Integer sort;

View File

@@ -56,6 +56,9 @@ public class RouteVO {
@Schema(description = "路由参数")
private Map<String,String> params;
@Schema(description = "外链地址")
private String externalUrl;
}
@Schema(description = "子路由列表")

View File

@@ -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)
// 验证和解析过滤器

View File

@@ -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);
}
}

View File

@@ -58,11 +58,4 @@ public interface ConfigService extends IService<Config> {
*/
boolean refreshCache();
/**
* 获取系统配置
* @param key 配置键
* @return 配置值
*/
Object getSystemConfig(String key);
}

View File

@@ -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;
}
}

View File

@@ -18,6 +18,7 @@
t1.route_name,
t1.route_path,
t1.component,
t1.external_url,
t1.icon,
t1.sort,
t1.visible,