Compare commits

...

31 Commits

Author SHA1 Message Date
孙小云 2b702197fa xx 2026-02-02 15:43:41 +08:00
孙小云 d80502c681 xx 2026-02-02 13:26:18 +08:00
孙小云 3ad0509eb0 添加wvp过滤 2026-02-02 13:19:15 +08:00
孙小云 095d9d257f xx 2026-01-31 14:41:50 +08:00
孙小云 8341928fb9 xx 2026-01-31 14:40:16 +08:00
孙小云 d05fe3808b xx 2026-01-26 16:42:05 +08:00
孙小云 d4f678260d xx 2026-01-26 16:40:28 +08:00
孙小云 ac251f64aa 添加minio的启动 2026-01-26 16:36:28 +08:00
孙小云 b3b28efc4c xx 2026-01-26 16:05:28 +08:00
孙小云 ec6eb4d379 xx 2026-01-26 15:55:10 +08:00
孙小云 70388626db 修改文案 2026-01-26 15:44:13 +08:00
孙小云 5a39002e40 xx 2026-01-26 14:37:26 +08:00
孙小云 80142472d8 xx 2026-01-26 14:22:57 +08:00
孙小云 32499ba9a8 xx 2026-01-26 14:14:14 +08:00
孙小云 ad0b0ff528 xx 2026-01-26 14:04:59 +08:00
孙小云 4e03af43c3 xx 2026-01-26 14:02:18 +08:00
孙小云 1f03ac86bd xx 2026-01-26 13:52:44 +08:00
孙小云 01c4c00c84 xx 2026-01-26 13:49:46 +08:00
孙小云 3e83ac1069 xx 2026-01-26 13:45:35 +08:00
孙小云 346859ba55 xx 2026-01-26 13:44:43 +08:00
孙小云 5ad6c2e9ee xx 2026-01-23 20:05:27 +08:00
孙小云 de67f3d5cb xx 2026-01-23 20:03:52 +08:00
孙小云 e7a33e89a6 2026-01-20 14:05:33 +08:00
孙小云 12c7674d53 修改代码,使网关配置白名单以后,页面也可以正确展示 2026-01-15 17:40:55 +08:00
孙小云 ff2234a5e9 删除校验 2026-01-15 13:42:19 +08:00
孙小云 33f7c623f9 x 2026-01-15 13:27:01 +08:00
孙小云 5d5a3e0543 移除验证码校验 2026-01-15 13:11:27 +08:00
孙小云 e0dbb593bf xx 2026-01-13 14:01:44 +08:00
孙小云 a03a8c3d71 xx 2026-01-13 13:47:09 +08:00
孙小云 d73209cba3 xx 2026-01-13 11:17:40 +08:00
孙小云 4a2d15d44e xx 2026-01-13 09:59:03 +08:00
6 changed files with 343 additions and 3 deletions

View File

@ -1 +1 @@
2d1234567891011
ddddddddddddddddddddddddddddddddddddddddddddddddddd=edddd2d1234567891011的堆堆

View File

@ -49,7 +49,25 @@ public class AuthFilter implements GlobalFilter, Ordered
// 跳过不需要验证的路径
if (StringUtils.matches(url, ignoreWhite.getWhites()))
{
return chain.filter(exchange);
// 白名单路径也尝试解析token并传递用户信息如果有token的话
String token = getToken(request);
if (StringUtils.isNotEmpty(token))
{
Claims claims = JwtUtils.parseToken(token);
if (claims != null)
{
String userkey = JwtUtils.getUserKey(claims);
String userid = JwtUtils.getUserId(claims);
String username = JwtUtils.getUserName(claims);
// 设置用户信息到请求
addHeader(mutate, SecurityConstants.USER_KEY, userkey);
addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);
addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);
}
}
// 内部请求来源参数清除
removeHeader(mutate, SecurityConstants.FROM_SOURCE);
return chain.filter(exchange.mutate().request(mutate.build()).build());
}
String token = getToken(request);
if (StringUtils.isEmpty(token))

View File

@ -45,6 +45,11 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
ServerHttpRequest request = exchange.getRequest();
// 非登录/注册请求或验证码关闭不处理
// 已禁用验证码校验直接放行
return chain.filter(exchange);
// 以下代码已注释不再校验验证码
/*
if (!StringUtils.equalsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled())
{
return chain.filter(exchange);
@ -61,6 +66,7 @@ public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
}
return chain.filter(exchange);
*/
};
}

View File

@ -0,0 +1,165 @@
package com.ruoyi.gateway.filter;
import com.ruoyi.gateway.utils.WvpTokenClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
/**
* WVP Access Token 过滤器
* 用于为 WVP 请求添加或替换 access-token 请求头
*/
@Component
public class WvpAccessTokenFilter extends AbstractGatewayFilterFactory<WvpAccessTokenFilter.Config> {
private static final Logger log = LoggerFactory.getLogger(WvpAccessTokenFilter.class);
/**
* WVP token 过期时间分钟- WVP 源码中获取
*/
private static final long TOKEN_EXPIRATION_MINUTES = 30;
/**
* 提前刷新时间分钟- token 过期前 5 分钟刷新
*/
private static final long REFRESH_BEFORE_EXPIRATION_MINUTES = 5;
private static String cachedAccessToken = null;
private static LocalDateTime tokenExpirationTime = null;
private static final Object LOCK = new Object();
private final WebClient webClient = WebClient.builder().build();
public WvpAccessTokenFilter() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
String path = exchange.getRequest().getURI().getPath();
String query = exchange.getRequest().getURI().getQuery();
String fullPath = query != null ? path + "?" + query : path;
log.info("WVP 请求 - 路径: {}", fullPath);
// 使用 WebClient 直接调用 WVP支持自动重试
return callWvpWithRetry(fullPath, exchange, 0)
.flatMap(responseBody -> {
// 将响应写回客户端
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
DataBufferFactory bufferFactory = response.bufferFactory();
DataBuffer buffer = bufferFactory.wrap(responseBody.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
})
.onErrorResume(e -> {
log.error("WVP 请求失败", e);
return chain.filter(exchange);
});
};
}
/**
* 调用 WVP 接口支持自动重试
*
* @param path 请求路径
* @param exchange ServerWebExchange
* @param retryCount 重试次数
* @return 响应体
*/
private Mono<String> callWvpWithRetry(String path, ServerWebExchange exchange, int retryCount) {
try {
String accessToken = getAccessToken();
String wvpUrl = "http://wvp-pro:18978" + path;
log.debug("调用 WVP 接口: {}, access-token: {}", wvpUrl, accessToken);
return webClient.get()
.uri(wvpUrl)
.header("access-token", accessToken)
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> {
log.error("WVP 请求失败,尝试刷新 token 并重试", e);
if (retryCount < 1) {
// 清除缓存的 token下次会重新获取
clearCachedToken();
// 重试一次
return callWvpWithRetry(path, exchange, retryCount + 1);
}
return Mono.error(e);
});
} catch (Exception e) {
log.error("获取 access token 失败", e);
return Mono.error(e);
}
}
/**
* 获取 access token使用缓存机制并支持提前刷新
* 如果缓存为空或即将过期则调用 WvpTokenClient.login 获取新的 token
*/
private String getAccessToken() throws Exception {
LocalDateTime now = LocalDateTime.now();
// 检查 token 是否需要刷新不存在或即将过期
if (cachedAccessToken == null || tokenExpirationTime == null ||
now.plusMinutes(REFRESH_BEFORE_EXPIRATION_MINUTES).isAfter(tokenExpirationTime)) {
synchronized (LOCK) {
// 双重检查
now = LocalDateTime.now();
if (cachedAccessToken == null || tokenExpirationTime == null ||
now.plusMinutes(REFRESH_BEFORE_EXPIRATION_MINUTES).isAfter(tokenExpirationTime)) {
log.info("Token 不存在或即将过期,正在刷新 WVP access token...");
WvpTokenClient.LoginResponse loginResponse = WvpTokenClient.login("admin", "admin");
if (loginResponse.getCode() == 0 && loginResponse.getData() != null) {
cachedAccessToken = loginResponse.getData().getAccessToken();
// 设置过期时间为当前时间 + TOKEN_EXPIRATION_MINUTES
tokenExpirationTime = now.plusMinutes(TOKEN_EXPIRATION_MINUTES);
log.info("WVP 登录成功,获取到 access token过期时间: {}", tokenExpirationTime);
} else {
throw new Exception("WVP 登录失败: " + loginResponse.getMsg());
}
}
}
}
return cachedAccessToken;
}
/**
* 清除缓存的 access token可用于 token 过期时重新获取
*/
public static void clearCachedToken() {
synchronized (LOCK) {
cachedAccessToken = null;
tokenExpirationTime = null;
log.info("已清除缓存的 WVP access token");
}
}
/**
* 配置类
*/
public static class Config {
}
}

View File

@ -0,0 +1,151 @@
package com.ruoyi.gateway.utils;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
/**
* WVP Token 客户端
* 用于获取 WVP-PRO access token
*/
public class WvpTokenClient {
private static final Logger log = LoggerFactory.getLogger(WvpTokenClient.class);
// WVP 服务地址 - 使用容器名称进行容器间通信
private static final String WVP_BASE_URL = "http://wvp-pro:18978";
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* WVP登录响应对象
*/
public static class LoginResponse {
private int code;
private String msg;
private LoginData data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public LoginData getData() {
return data;
}
public void setData(LoginData data) {
this.data = data;
}
}
/**
* 登录数据对象
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public static class LoginData {
private String accessToken;
private String username;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
/**
* 计算MD5哈希值
*
* @param input 输入字符串
* @return MD5哈希值小写
* @throws Exception 如果计算失败
*/
private static String md5(String input) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(input.getBytes(StandardCharsets.UTF_8));
StringBuilder hexString = new StringBuilder();
for (byte b : messageDigest) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
/**
* WVP用户登录
*
* @param username 用户名
* @param password 密码明文
* @return 登录响应对象
* @throws Exception 如果请求失败
*/
public static LoginResponse login(String username, String password) throws Exception {
// 计算密码的MD5值
String passwordMd5 = md5(password);
// 构建 URL 参数
StringBuilder urlBuilder = new StringBuilder(WVP_BASE_URL);
urlBuilder.append("/api/user/login?");
urlBuilder.append("username=").append(URLEncoder.encode(username, StandardCharsets.UTF_8.name()));
urlBuilder.append("&password=").append(passwordMd5);
// 创建 HTTP 连接
URL url = new URL(urlBuilder.toString());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
// 获取响应
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
// 解析JSON响应
return objectMapper.readValue(response.toString(), LoginResponse.class);
} else {
throw new Exception("HTTP 请求失败,状态码: " + responseCode);
}
}
}

View File

@ -1,6 +1,6 @@
Spring Boot Version: ${spring-boot.version}
Spring Application Name: ${spring.application.name}
_ _
_ _
(_) | |
_ __ _ _ ___ _ _ _ ______ __ _ __ _ | |_ ___ __ __ __ _ _ _
| '__|| | | | / _ \ | | | || ||______| / _` | / _` || __| / _ \\ \ /\ / / / _` || | | |