Browse Source

修改

master
dimengzhe 6 months ago
parent
commit
469baefb0d
  1. 38
      pom.xml
  2. 223
      src/main/java/com/yxt/ss/gateway/api/AuthFilter.java
  3. 3
      src/main/java/com/yxt/ss/gateway/api/GatewayApiApplication.java
  4. 25
      src/main/java/com/yxt/ss/gateway/api/authutils/CacheConstants.java
  5. 90
      src/main/java/com/yxt/ss/gateway/api/authutils/CharsetKit.java
  6. 849
      src/main/java/com/yxt/ss/gateway/api/authutils/Convert.java
  7. 41
      src/main/java/com/yxt/ss/gateway/api/authutils/IgnoreWhiteProperties.java
  8. 75
      src/main/java/com/yxt/ss/gateway/api/authutils/StrFormatter.java
  9. 525
      src/main/java/com/yxt/ss/gateway/api/authutils/StringUtils.java
  10. 128
      src/main/java/com/yxt/ss/gateway/api/rest/ApiTestRest.java
  11. 6
      src/main/java/com/yxt/ss/gateway/api/rest/ClientRest.java
  12. 8
      src/main/java/com/yxt/ss/gateway/api/service/ClientService.java
  13. 63
      src/main/java/com/yxt/ss/gateway/api/service/Signature.java
  14. 4
      src/main/java/com/yxt/ss/gateway/api/utils/AppKeyConfig.java
  15. 40
      src/main/java/com/yxt/ss/gateway/api/utils/SignatureUtil.java
  16. 29
      src/main/java/com/yxt/ss/gateway/api/utils/WebFluxLoggingConfig.java
  17. 2
      src/main/resources/application-devv.yml
  18. 21
      src/main/resources/application.yml

38
pom.xml

@ -20,10 +20,10 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- <dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@ -34,12 +34,42 @@
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<!--引入redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.11.0</version> <!-- 请根据需要选择最新版本 -->
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>

223
src/main/java/com/yxt/ss/gateway/api/AuthFilter.java

@ -0,0 +1,223 @@
package com.yxt.ss.gateway.api;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yxt.ss.gateway.api.authutils.*;
import com.yxt.ss.gateway.api.utils.AppKeyConfig;
import com.yxt.ss.gateway.api.utils.ResultBean;
import com.yxt.ss.gateway.api.utils.SignatureUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
/**
* @author dimengzhe
* @description 网关鉴权
*/
@Component
public class AuthFilter implements GlobalFilter, Ordered {
private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);
@Autowired
private IgnoreWhiteProperties ignoreWhite;
@Autowired
private AppKeyConfig appKeyConfig;
public String getSecret(String appKey) {
return appKeyConfig.getKeys().get(appKey);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String url = exchange.getRequest().getURI().getPath();
// 1. URI 白名单过滤:如果请求路径在白名单中,直接放行
if (isWhitelisted(url)) {
return chain.filter(exchange);
}
// 2. 提取请求参数并进行验证
return extractParameters(exchange)
.flatMap(parameters -> {
// 校验请求参数
ResultBean validationResult = validate(parameters);
// 校验失败,返回 401 Unauthorized 错误响应
if (!validationResult.getSuccess()) {
return setUnauthorizedResponse(exchange, validationResult.getMsg());
}
// 3. 如果需要,可以从参数中提取信息并添加到请求头
ServerHttpRequest mutableReq = exchange.getRequest().mutate()
.header(CacheConstants._APP, parameters.get("_app"))
.build();
ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
// 4. 继续执行后续过滤器链
return chain.filter(mutableExchange);
});
}
// 提取请求参数方法:根据请求类型 (GET 或 POST) 提取参数
private Mono<Map<String, String>> extractParameters(ServerWebExchange exchange) {
HttpMethod method = exchange.getRequest().getMethod();
// 1. 如果是 GET 请求,从 URL 查询参数中提取
if (method == HttpMethod.GET) {
return Mono.just(exchange.getRequest().getQueryParams().toSingleValueMap());
}
// 如果是 POST、PUT 或 DELETE 请求,从请求体中提取参数
if (exchange.getRequest().getMethod() == HttpMethod.POST ||
exchange.getRequest().getMethod() == HttpMethod.PUT ||
exchange.getRequest().getMethod() == HttpMethod.DELETE) {
String contentType = exchange.getRequest().getHeaders().getContentType().toString();
if (contentType.contains("application/json")) {
return exchange.getRequest().getBody()
.collectList()
.map(dataBuffers -> {
// 将 DataBuffer 转换为字符串
StringBuilder bodyBuilder = new StringBuilder();
dataBuffers.forEach(dataBuffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());
bodyBuilder.append(charBuffer);
});
// 解析 JSON 请求体
try {
return parseJsonBody(bodyBuilder.toString());
} catch (IOException e) {
e.printStackTrace();
}
return new HashMap<String, String>(); // 如果解析失败,返回空 Map
});
} else if (contentType.contains("application/x-www-form-urlencoded")) {
// 处理 x-www-form-urlencoded 格式
return exchange.getRequest().getBody()
.collectList()
.map(dataBuffers -> {
StringBuilder bodyBuilder = new StringBuilder();
dataBuffers.forEach(dataBuffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());
bodyBuilder.append(charBuffer);
});
try {
return parseFormBody(bodyBuilder.toString());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return new HashMap<String, String>();
});
}
}
// 其他情况返回空 Map
return Mono.just(new HashMap<>());
}
//解析 JSON 请求体的方法
private Map<String, String> parseJsonBody(String body) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(body, Map.class);
}
// 辅助方法:解析 form-data 格式字符串
private Map<String, String> parseFormBody(String body) throws UnsupportedEncodingException {
Map<String, String> parameters = new HashMap<>();
// 按照 & 分割每一对键值
String[] pairs = body.split("&");
for (String pair : pairs) {
String[] keyValue = pair.split("=");
if (keyValue.length == 2) {
// 解码键和值
parameters.put(URLDecoder.decode(keyValue[0], "UTF-8"),
URLDecoder.decode(keyValue[1], "UTF-8"));
}
}
return parameters;
}
// 设置未授权响应方法
private Mono<Void> setUnauthorizedResponse(ServerWebExchange exchange, String message) {
// 设置响应状态码为 401 Unauthorized
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 设置响应内容类型为 JSON
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
// 创建返回的错误信息
DataBuffer buffer = exchange.getResponse().bufferFactory()
.wrap(("{\"error\":\"" + message + "\"}").getBytes(StandardCharsets.UTF_8));
return exchange.getResponse().writeWith(Mono.just(buffer));
}
private boolean isWhitelisted(String url) {
return StringUtils.matches(url, ignoreWhite.getWhites()) ||
StringUtils.matchesTwo(url, ignoreWhite.getWhitesTwo());
}
@Override
public int getOrder() {
return 0;
}
ResultBean validate(Map<String, String> data) {
ResultBean rb = ResultBean.fireFail();
// 解析参数
String app = data.get("_app");
if (org.springframework.util.StringUtils.isEmpty(app)) {
return rb.setMsg("_app参数缺失或无效");
}
// 获取 secret 值
String secret = getSecret(app);
if (org.springframework.util.StringUtils.isEmpty(secret)) {
return rb.setMsg("_app参数不正确");
}
// 校验时间戳 _t 参数
String timestampStr = data.get("_t");
if (org.springframework.util.StringUtils.isEmpty(timestampStr)) {
return rb.setMsg("_t参数缺失");
}
long timestamp;
try {
timestamp = Long.parseLong(timestampStr);
} catch (NumberFormatException e) {
return rb.setMsg("_t参数格式不正确");
}
// 时间范围校验
long currentTimestamp = Instant.now().getEpochSecond();
long timeDifference = Math.abs(currentTimestamp - timestamp);
final int ALLOWED_TIME_DIFF = 300; // 最大允许时间偏差(秒)
if (timeDifference > ALLOWED_TIME_DIFF) {
return rb.setMsg("时间已超过5分钟,时间失效");
}
// 签名验证
ResultBean<Boolean> resultBean = SignatureUtil.validateSignature(data, secret);
if (!resultBean.getSuccess()) {
return rb.setMsg(resultBean.getMsg());
}
return rb.success();
}
}

3
src/main/java/com/yxt/ss/gateway/api/GatewayApiApplication.java

@ -10,11 +10,12 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
* @author: dimengzhe
* @date: 2024/12/6
**/
//@EnableDiscoveryClient
@EnableDiscoveryClient
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class GatewayApiApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApiApplication.class, args);
}
}

25
src/main/java/com/yxt/ss/gateway/api/authutils/CacheConstants.java

@ -0,0 +1,25 @@
package com.yxt.ss.gateway.api.authutils;
/**
* @author dimengzhe
* @description 缓存的key 常量
*/
public class CacheConstants {
/**
* 令牌自定义标识
*/
public static final String HEADER = "token";
/**
* 令牌前缀
*/
public static final String TOKEN_PREFIX = "Bearer ";
/**
* 用户名字段
*/
public static final String DETAILS_USERNAME = "userName";
public static final String _APP = "_app";
}

90
src/main/java/com/yxt/ss/gateway/api/authutils/CharsetKit.java

@ -0,0 +1,90 @@
package com.yxt.ss.gateway.api.authutils;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* @author dimengzhe
* @description
*/
public class CharsetKit {
/**
* ISO-8859-1
*/
public static final String ISO_8859_1 = "ISO-8859-1";
/**
* UTF-8
*/
public static final String UTF_8 = "UTF-8";
/**
* GBK
*/
public static final String GBK = "GBK";
/**
* ISO-8859-1
*/
public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1);
/**
* UTF-8
*/
public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8);
/**
* GBK
*/
public static final Charset CHARSET_GBK = Charset.forName(GBK);
/**
* 转换为Charset对象
*
* @param charset 字符集为空则返回默认字符集
* @return Charset
*/
public static Charset charset(String charset) {
return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset);
}
/**
* 转换字符串的字符集编码
*
* @param source 字符串
* @param srcCharset 源字符集默认ISO-8859-1
* @param destCharset 目标字符集默认UTF-8
* @return 转换后的字符集
*/
public static String convert(String source, String srcCharset, String destCharset) {
return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset));
}
/**
* 转换字符串的字符集编码
*
* @param source 字符串
* @param srcCharset 源字符集默认ISO-8859-1
* @param destCharset 目标字符集默认UTF-8
* @return 转换后的字符集
*/
public static String convert(String source, Charset srcCharset, Charset destCharset) {
if (null == srcCharset) {
srcCharset = StandardCharsets.ISO_8859_1;
}
if (null == destCharset) {
destCharset = StandardCharsets.UTF_8;
}
if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) {
return source;
}
return new String(source.getBytes(srcCharset), destCharset);
}
/**
* @return 系统字符集编码
*/
public static String systemCharset() {
return Charset.defaultCharset().name();
}
}

849
src/main/java/com/yxt/ss/gateway/api/authutils/Convert.java

@ -0,0 +1,849 @@
package com.yxt.ss.gateway.api.authutils;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.text.NumberFormat;
import java.util.Set;
/**
* @author dimengzhe
* @description
*/
public class Convert {
/**
* 转换为字符串<br>
* 如果给定的值为null或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static String toStr(Object value, String defaultValue) {
if (null == value) {
return defaultValue;
}
if (value instanceof String) {
return (String) value;
}
return value.toString();
}
/**
* 转换为字符串<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static String toStr(Object value) {
return toStr(value, null);
}
/**
* 转换为字符<br>
* 如果给定的值为null或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Character toChar(Object value, Character defaultValue) {
if (null == value) {
return defaultValue;
}
if (value instanceof Character) {
return (Character) value;
}
final String valueStr = toStr(value, null);
return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0);
}
/**
* 转换为字符<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Character toChar(Object value) {
return toChar(value, null);
}
/**
* 转换为byte<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Byte toByte(Object value, Byte defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Byte) {
return (Byte) value;
}
if (value instanceof Number) {
return ((Number) value).byteValue();
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return Byte.parseByte(valueStr);
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为byte<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Byte toByte(Object value) {
return toByte(value, null);
}
/**
* 转换为Short<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Short toShort(Object value, Short defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Short) {
return (Short) value;
}
if (value instanceof Number) {
return ((Number) value).shortValue();
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return Short.parseShort(valueStr.trim());
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为Short<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Short toShort(Object value) {
return toShort(value, null);
}
/**
* 转换为Number<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Number toNumber(Object value, Number defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Number) {
return (Number) value;
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return NumberFormat.getInstance().parse(valueStr);
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为Number<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Number toNumber(Object value) {
return toNumber(value, null);
}
/**
* 转换为int<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Integer toInt(Object value, Integer defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Integer) {
return (Integer) value;
}
if (value instanceof Number) {
return ((Number) value).intValue();
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return Integer.parseInt(valueStr.trim());
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为int<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Integer toInt(Object value) {
return toInt(value, null);
}
/**
* 转换为Integer数组<br>
*
* @param str 被转换的值
* @return 结果
*/
public static Integer[] toIntArray(String str) {
return toIntArray(",", str);
}
/**
* 转换为Long数组<br>
*
* @param str 被转换的值
* @return 结果
*/
public static Long[] toLongArray(String str) {
return toLongArray(",", str);
}
/**
* 转换为Integer数组<br>
*
* @param split 分隔符
* @param split 被转换的值
* @return 结果
*/
public static Integer[] toIntArray(String split, String str) {
if (StringUtils.isEmpty(str)) {
return new Integer[]{};
}
String[] arr = str.split(split);
final Integer[] ints = new Integer[arr.length];
for (int i = 0; i < arr.length; i++) {
final Integer v = toInt(arr[i], 0);
ints[i] = v;
}
return ints;
}
/**
* 转换为Long数组<br>
*
* @param split 分隔符
* @param str 被转换的值
* @return 结果
*/
public static Long[] toLongArray(String split, String str) {
if (StringUtils.isEmpty(str)) {
return new Long[]{};
}
String[] arr = str.split(split);
final Long[] longs = new Long[arr.length];
for (int i = 0; i < arr.length; i++) {
final Long v = toLong(arr[i], null);
longs[i] = v;
}
return longs;
}
/**
* 转换为String数组<br>
*
* @param str 被转换的值
* @return 结果
*/
public static String[] toStrArray(String str) {
return toStrArray(",", str);
}
/**
* 转换为String数组<br>
*
* @param split 分隔符
* @param split 被转换的值
* @return 结果
*/
public static String[] toStrArray(String split, String str) {
return str.split(split);
}
/**
* 转换为long<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Long toLong(Object value, Long defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Long) {
return (Long) value;
}
if (value instanceof Number) {
return ((Number) value).longValue();
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
// 支持科学计数法
return new BigDecimal(valueStr.trim()).longValue();
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为long<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Long toLong(Object value) {
return toLong(value, null);
}
/**
* 转换为double<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Double toDouble(Object value, Double defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Double) {
return (Double) value;
}
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
// 支持科学计数法
return new BigDecimal(valueStr.trim()).doubleValue();
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为double<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Double toDouble(Object value) {
return toDouble(value, null);
}
/**
* 转换为Float<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Float toFloat(Object value, Float defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Float) {
return (Float) value;
}
if (value instanceof Number) {
return ((Number) value).floatValue();
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return Float.parseFloat(valueStr.trim());
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为Float<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Float toFloat(Object value) {
return toFloat(value, null);
}
/**
* 转换为boolean<br>
* String支持的值为truefalseyesokno1,0 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static Boolean toBool(Object value, Boolean defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Boolean) {
return (Boolean) value;
}
String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
valueStr = valueStr.trim().toLowerCase();
switch (valueStr) {
case "true":
return true;
case "false":
return false;
case "yes":
return true;
case "ok":
return true;
case "no":
return false;
case "1":
return true;
case "0":
return false;
default:
return defaultValue;
}
}
/**
* 转换为boolean<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static Boolean toBool(Object value) {
return toBool(value, null);
}
/**
* 转换为Enum对象<br>
* 如果给定的值为空或者转换失败返回默认值<br>
*
* @param clazz Enum的Class
* @param value
* @param defaultValue 默认值
* @return Enum
*/
public static <E extends Enum<E>> E toEnum(Class<E> clazz, Object value, E defaultValue) {
if (value == null) {
return defaultValue;
}
if (clazz.isAssignableFrom(value.getClass())) {
@SuppressWarnings("unchecked")
E myE = (E) value;
return myE;
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return Enum.valueOf(clazz, valueStr);
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为Enum对象<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
*
* @param clazz Enum的Class
* @param value
* @return Enum
*/
public static <E extends Enum<E>> E toEnum(Class<E> clazz, Object value) {
return toEnum(clazz, value, null);
}
/**
* 转换为BigInteger<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static BigInteger toBigInteger(Object value, BigInteger defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof BigInteger) {
return (BigInteger) value;
}
if (value instanceof Long) {
return BigInteger.valueOf((Long) value);
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return new BigInteger(valueStr);
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为BigInteger<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static BigInteger toBigInteger(Object value) {
return toBigInteger(value, null);
}
/**
* 转换为BigDecimal<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @param defaultValue 转换错误时的默认值
* @return 结果
*/
public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof BigDecimal) {
return (BigDecimal) value;
}
if (value instanceof Long) {
return new BigDecimal((Long) value);
}
if (value instanceof Double) {
return new BigDecimal((Double) value);
}
if (value instanceof Integer) {
return new BigDecimal((Integer) value);
}
final String valueStr = toStr(value, null);
if (StringUtils.isEmpty(valueStr)) {
return defaultValue;
}
try {
return new BigDecimal(valueStr);
} catch (Exception e) {
return defaultValue;
}
}
/**
* 转换为BigDecimal<br>
* 如果给定的值为空或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
* @return 结果
*/
public static BigDecimal toBigDecimal(Object value) {
return toBigDecimal(value, null);
}
/**
* 将对象转为字符串<br>
* 1Byte数组和ByteBuffer会被转换为对应字符串的数组 2对象数组会调用Arrays.toString方法
*
* @param obj 对象
* @return 字符串
*/
public static String utf8Str(Object obj) {
return str(obj, CharsetKit.CHARSET_UTF_8);
}
/**
* 将对象转为字符串<br>
* 1Byte数组和ByteBuffer会被转换为对应字符串的数组 2对象数组会调用Arrays.toString方法
*
* @param obj 对象
* @param charsetName 字符集
* @return 字符串
*/
public static String str(Object obj, String charsetName) {
return str(obj, Charset.forName(charsetName));
}
/**
* 将对象转为字符串<br>
* 1Byte数组和ByteBuffer会被转换为对应字符串的数组 2对象数组会调用Arrays.toString方法
*
* @param obj 对象
* @param charset 字符集
* @return 字符串
*/
public static String str(Object obj, Charset charset) {
if (null == obj) {
return null;
}
if (obj instanceof String) {
return (String) obj;
} else if (obj instanceof byte[] || obj instanceof Byte[]) {
return str((Byte[]) obj, charset);
} else if (obj instanceof ByteBuffer) {
return str((ByteBuffer) obj, charset);
}
return obj.toString();
}
/**
* 将byte数组转为字符串
*
* @param bytes byte数组
* @param charset 字符集
* @return 字符串
*/
public static String str(byte[] bytes, String charset) {
return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset));
}
/**
* 解码字节码
*
* @param data 字符串
* @param charset 字符集如果此字段为空则解码的结果取决于平台
* @return 解码后的字符串
*/
public static String str(byte[] data, Charset charset) {
if (data == null) {
return null;
}
if (null == charset) {
return new String(data);
}
return new String(data, charset);
}
/**
* 将编码的byteBuffer数据转换为字符串
*
* @param data 数据
* @param charset 字符集如果为空使用当前系统字符集
* @return 字符串
*/
public static String str(ByteBuffer data, String charset) {
if (data == null) {
return null;
}
return str(data, Charset.forName(charset));
}
/**
* 将编码的byteBuffer数据转换为字符串
*
* @param data 数据
* @param charset 字符集如果为空使用当前系统字符集
* @return 字符串
*/
public static String str(ByteBuffer data, Charset charset) {
if (null == charset) {
charset = Charset.defaultCharset();
}
return charset.decode(data).toString();
}
// ----------------------------------------------------------------------- 全角半角转换
/**
* 半角转全角
*
* @param input String.
* @return 全角字符串.
*/
public static String toSBC(String input) {
return toSBC(input, null);
}
/**
* 半角转全角
*
* @param input String
* @param notConvertSet 不替换的字符集合
* @return 全角字符串.
*/
public static String toSBC(String input, Set<Character> notConvertSet) {
char c[] = input.toCharArray();
for (int i = 0; i < c.length; i++) {
if (null != notConvertSet && notConvertSet.contains(c[i])) {
// 跳过不替换的字符
continue;
}
if (c[i] == ' ') {
c[i] = '\u3000';
} else if (c[i] < '\177') {
c[i] = (char) (c[i] + 65248);
}
}
return new String(c);
}
/**
* 全角转半角
*
* @param input String.
* @return 半角字符串
*/
public static String toDBC(String input) {
return toDBC(input, null);
}
/**
* 替换全角为半角
*
* @param text 文本
* @param notConvertSet 不替换的字符集合
* @return 替换后的字符
*/
public static String toDBC(String text, Set<Character> notConvertSet) {
char c[] = text.toCharArray();
for (int i = 0; i < c.length; i++) {
if (null != notConvertSet && notConvertSet.contains(c[i])) {
// 跳过不替换的字符
continue;
}
if (c[i] == '\u3000') {
c[i] = ' ';
} else if (c[i] > '\uFF00' && c[i] < '\uFF5F') {
c[i] = (char) (c[i] - 65248);
}
}
String returnString = new String(c);
return returnString;
}
/**
* 数字金额大写转换 先写个完整的然后将如零拾替换成零
*
* @param n 数字
* @return 中文大写数字
*/
public static String digitUppercase(double n) {
String[] fraction = {"角", "分"};
String[] digit = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"};
String[][] unit = {{"元", "万", "亿"}, {"", "拾", "佰", "仟"}};
String head = n < 0 ? "负" : "";
n = Math.abs(n);
String s = "";
for (int i = 0; i < fraction.length; i++) {
s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", "");
}
if (s.length() < 1) {
s = "整";
}
int integerPart = (int) Math.floor(n);
for (int i = 0; i < unit[0].length && integerPart > 0; i++) {
String p = "";
for (int j = 0; j < unit[1].length && n > 0; j++) {
p = digit[integerPart % 10] + unit[1][j] + p;
integerPart = integerPart / 10;
}
s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s;
}
return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整");
}
}

41
src/main/java/com/yxt/ss/gateway/api/authutils/IgnoreWhiteProperties.java

@ -0,0 +1,41 @@
package com.yxt.ss.gateway.api.authutils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* @author dimengzhe
* @description 放行白名单配置
*/
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "ignore")
public class IgnoreWhiteProperties {
/**
* 放行白名单配置网关不校验此处的白名单
*/
private List<String> whites = new ArrayList<>();
public List<String> getWhites() {
return whites;
}
public void setWhites(List<String> whites) {
this.whites = whites;
}
private List<String> whitesTwo = new ArrayList<>();
public List<String> getWhitesTwo() {
return whitesTwo;
}
public void setWhitesTwo(List<String> whitesTwo) {
this.whitesTwo = whitesTwo;
}
}

75
src/main/java/com/yxt/ss/gateway/api/authutils/StrFormatter.java

@ -0,0 +1,75 @@
package com.yxt.ss.gateway.api.authutils;
/**
* @author dimengzhe
* @description
*/
public class StrFormatter {
public static final String EMPTY_JSON = "{}";
public static final char C_BACKSLASH = '\\';
public static final char C_DELIM_START = '{';
public static final char C_DELIM_END = '}';
/**
* 格式化字符串<br>
* 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
* 如果想输出 {} 使用 \\转义 { 即可如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
* <br>
* 通常使用format("this is {} for {}", "a", "b") -> this is a for b<br>
* 转义{} format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
* 转义\ format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
*
* @param strPattern 字符串模板
* @param argArray 参数列表
* @return 结果
*/
public static String format(final String strPattern, final Object... argArray) {
if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray)) {
return strPattern;
}
final int strPatternLength = strPattern.length();
// 初始化定义好的长度以获得更好的性能
StringBuilder sbuf = new StringBuilder(strPatternLength + 50);
int handledPosition = 0;
int delimIndex;// 占位符所在位置
for (int argIndex = 0; argIndex < argArray.length; argIndex++) {
delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition);
if (delimIndex == -1) {
if (handledPosition == 0) {
return strPattern;
} else { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果
sbuf.append(strPattern, handledPosition, strPatternLength);
return sbuf.toString();
}
} else {
if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) {
if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) {
// 转义符之前还有一个转义符,占位符依旧有效
sbuf.append(strPattern, handledPosition, delimIndex - 1);
sbuf.append(Convert.utf8Str(argArray[argIndex]));
handledPosition = delimIndex + 2;
} else {
// 占位符被转义
argIndex--;
sbuf.append(strPattern, handledPosition, delimIndex - 1);
sbuf.append(C_DELIM_START);
handledPosition = delimIndex + 1;
}
} else {
// 正常占位符
sbuf.append(strPattern, handledPosition, delimIndex);
sbuf.append(Convert.utf8Str(argArray[argIndex]));
handledPosition = delimIndex + 2;
}
}
}
// 加入最后一个占位符后所有的字符
sbuf.append(strPattern, handledPosition, strPattern.length());
return sbuf.toString();
}
}

525
src/main/java/com/yxt/ss/gateway/api/authutils/StringUtils.java

@ -0,0 +1,525 @@
package com.yxt.ss.gateway.api.authutils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* @author dimengzhe
* @description
*/
public class StringUtils extends org.apache.commons.lang3.StringUtils {
/**
* 空字符串
*/
private static final String NULLSTR = "";
/**
* 下划线
*/
private static final char SEPARATOR = '_';
/**
* 星号
*/
private static final String START = "*";
/**
* 获取参数不为空值
*
* @param value defaultValue 要判断的value
* @return value 返回值
*/
public static <T> T nvl(T value, T defaultValue) {
return value != null ? value : defaultValue;
}
/**
* * 判断一个Collection是否为空 包含ListSetQueue
*
* @param coll 要判断的Collection
* @return true为空 false非空
*/
public static boolean isEmpty(Collection<?> coll) {
return isNull(coll) || coll.isEmpty();
}
/**
* * 判断一个Collection是否非空包含ListSetQueue
*
* @param coll 要判断的Collection
* @return true非空 false
*/
public static boolean isNotEmpty(Collection<?> coll) {
return !isEmpty(coll);
}
/**
* * 判断一个对象数组是否为空
*
* @param objects 要判断的对象数组
* * @return true为空 false非空
*/
public static boolean isEmpty(Object[] objects) {
return isNull(objects) || (objects.length == 0);
}
/**
* * 判断一个对象数组是否非空
*
* @param objects 要判断的对象数组
* @return true非空 false
*/
public static boolean isNotEmpty(Object[] objects) {
return !isEmpty(objects);
}
/**
* * 判断一个Map是否为空
*
* @param map 要判断的Map
* @return true为空 false非空
*/
public static boolean isEmpty(Map<?, ?> map) {
return isNull(map) || map.isEmpty();
}
/**
* * 判断一个Map是否为空
*
* @param map 要判断的Map
* @return true非空 false
*/
public static boolean isNotEmpty(Map<?, ?> map) {
return !isEmpty(map);
}
/**
* * 判断一个字符串是否为空串
*
* @param str String
* @return true为空 false非空
*/
public static boolean isEmpty(String str) {
return isNull(str) || NULLSTR.equals(str.trim());
}
/**
* * 判断一个字符串是否为非空串
*
* @param str String
* @return true非空串 false空串
*/
public static boolean isNotEmpty(String str) {
return !isEmpty(str);
}
/**
* * 判断一个对象是否为空
*
* @param object Object
* @return true为空 false非空
*/
public static boolean isNull(Object object) {
return object == null;
}
/**
* * 判断一个对象是否非空
*
* @param object Object
* @return true非空 false
*/
public static boolean isNotNull(Object object) {
return !isNull(object);
}
/**
* * 判断一个对象是否是数组类型Java基本型别的数组
*
* @param object 对象
* @return true是数组 false不是数组
*/
public static boolean isArray(Object object) {
return isNotNull(object) && object.getClass().isArray();
}
/**
* 去空格
*/
public static String trim(String str) {
return (str == null ? "" : str.trim());
}
/**
* 截取字符串
*
* @param str 字符串
* @param start 开始
* @return 结果
*/
public static String substring(final String str, int start) {
if (str == null) {
return NULLSTR;
}
if (start < 0) {
start = str.length() + start;
}
if (start < 0) {
start = 0;
}
if (start > str.length()) {
return NULLSTR;
}
return str.substring(start);
}
/**
* 截取字符串
*
* @param str 字符串
* @param start 开始
* @param end 结束
* @return 结果
*/
public static String substring(final String str, int start, int end) {
if (str == null) {
return NULLSTR;
}
if (end < 0) {
end = str.length() + end;
}
if (start < 0) {
start = str.length() + start;
}
if (end > str.length()) {
end = str.length();
}
if (start > end) {
return NULLSTR;
}
if (start < 0) {
start = 0;
}
if (end < 0) {
end = 0;
}
return str.substring(start, end);
}
/**
* 格式化文本, {} 表示占位符<br>
* 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
* 如果想输出 {} 使用 \\转义 { 即可如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
* <br>
* 通常使用format("this is {} for {}", "a", "b") -> this is a for b<br>
* 转义{} format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
* 转义\ format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
*
* @param template 文本模板被替换的部分用 {} 表示
* @param params 参数值
* @return 格式化后的文本
*/
public static String format(String template, Object... params) {
if (isEmpty(params) || isEmpty(template)) {
return template;
}
return StrFormatter.format(template, params);
}
/**
* 下划线转驼峰命名
*/
public static String toUnderScoreCase(String str) {
if (str == null) {
return null;
}
StringBuilder sb = new StringBuilder();
// 前置字符是否大写
boolean preCharIsUpperCase = true;
// 当前字符是否大写
boolean curreCharIsUpperCase = true;
// 下一字符是否大写
boolean nexteCharIsUpperCase = true;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (i > 0) {
preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
} else {
preCharIsUpperCase = false;
}
curreCharIsUpperCase = Character.isUpperCase(c);
if (i < (str.length() - 1)) {
nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
}
if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) {
sb.append(SEPARATOR);
} else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) {
sb.append(SEPARATOR);
}
sb.append(Character.toLowerCase(c));
}
return sb.toString();
}
/**
* 是否包含字符串
*
* @param str 验证字符串
* @param strs 字符串组
* @return 包含返回true
*/
public static boolean inStringIgnoreCase(String str, String... strs) {
if (str != null && strs != null) {
for (String s : strs) {
if (str.equalsIgnoreCase(trim(s))) {
return true;
}
}
}
return false;
}
/**
* 将下划线大写方式命名的字符串转换为驼峰式如果转换前的下划线大写方式命名的字符串为空则返回空字符串 例如HELLO_WORLD->HelloWorld
*
* @param name 转换前的下划线大写方式命名的字符串
* @return 转换后的驼峰式命名的字符串
*/
public static String convertToCamelCase(String name) {
StringBuilder result = new StringBuilder();
// 快速检查
if (name == null || name.isEmpty()) {
// 没必要转换
return "";
} else if (!name.contains("_")) {
// 不含下划线,仅将首字母大写
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
// 用下划线将原始字符串分割
String[] camels = name.split("_");
for (String camel : camels) {
// 跳过原始字符串中开头、结尾的下换线或双重下划线
if (camel.isEmpty()) {
continue;
}
// 首字母大写
result.append(camel.substring(0, 1).toUpperCase());
result.append(camel.substring(1).toLowerCase());
}
return result.toString();
}
/**
* 驼峰式命名法 例如user_name->userName
*/
public static String toCamelCase(String s) {
if (s == null) {
return null;
}
s = s.toLowerCase();
StringBuilder sb = new StringBuilder(s.length());
boolean upperCase = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == SEPARATOR) {
upperCase = true;
} else if (upperCase) {
sb.append(Character.toUpperCase(c));
upperCase = false;
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
*
* @param str 指定字符串
* @param strs 需要检查的字符串数组
* @return 是否匹配
*/
public static boolean matchesTwo(String str, List<String> strs) {
if (isEmpty(str) || isEmpty(strs)) {
return false;
}
for (String testStr : strs) {
if (matchesTwo(str, testStr)) {
return true;
}
}
return false;
}
public static boolean matches(String str, List<String> strs) {
if (isEmpty(str) || isEmpty(strs)) {
return false;
}
for (String testStr : strs) {
if (matches(str, testStr)) {
return true;
}
}
return false;
}
/**
* 查找指定字符串是否匹配指定字符串数组中的任意一个字符串
*
* @param str 指定字符串
* @param strs 需要检查的字符串数组
* @return 是否匹配
*/
public static boolean matches(String str, String... strs) {
if (isEmpty(str) || isEmpty(strs)) {
return false;
}
for (String testStr : strs) {
if (matches(str, testStr)) {
return true;
}
}
return false;
}
public static boolean matches(String str, String pattern) {
if (isEmpty(pattern) || isEmpty(str)) {
return false;
}
pattern = pattern.replaceAll("\\s*", ""); // 替换空格
int beginOffset = 0; // pattern截取开始位置
int formerStarOffset = -1; // 前星号的偏移位置
int latterStarOffset = -1; // 后星号的偏移位置
String remainingURI = str;
String prefixPattern = "";
String suffixPattern = "";
boolean result = false;
do {
formerStarOffset = indexOf(pattern, START, beginOffset);
prefixPattern = substring(pattern, beginOffset, formerStarOffset > -1 ? formerStarOffset : pattern.length());
// 匹配前缀Pattern
result = remainingURI.equals(prefixPattern);
// 已经没有星号,直接返回
if (formerStarOffset == -1) {
return result;
}
// 匹配失败,直接返回
if (!result) {
return false;
}
if (!isEmpty(prefixPattern)) {
remainingURI = substringAfter(str, prefixPattern);
}
// 匹配后缀Pattern
latterStarOffset = indexOf(pattern, START, formerStarOffset + 1);
suffixPattern = substring(pattern, formerStarOffset + 1, latterStarOffset > -1 ? latterStarOffset : pattern.length());
result = remainingURI.equals(suffixPattern);
// 匹配失败,直接返回
if (!result) {
return false;
}
if (!isEmpty(suffixPattern)) {
remainingURI = substringAfter(str, suffixPattern);
}
// 移动指针
beginOffset = latterStarOffset + 1;
}
while (!isEmpty(suffixPattern) && !isEmpty(remainingURI));
return true;
}
/**
* 查找指定字符串是否匹配
*
* @param str 指定字符串
* @param pattern 需要检查的字符串
* @return 是否匹配
*/
public static boolean matchesTwo(String str, String pattern) {
if (isEmpty(pattern) || isEmpty(str)) {
return false;
}
pattern = pattern.replaceAll("\\s*", ""); // 替换空格
int beginOffset = 0; // pattern截取开始位置
int formerStarOffset = -1; // 前星号的偏移位置
int latterStarOffset = -1; // 后星号的偏移位置
String remainingURI = str;
String prefixPattern = "";
String suffixPattern = "";
boolean result = false;
do {
formerStarOffset = indexOf(pattern, START, beginOffset);
prefixPattern = substring(pattern, beginOffset, formerStarOffset > -1 ? formerStarOffset : pattern.length());
// 匹配前缀Pattern
result = remainingURI.contains(prefixPattern);
// 已经没有星号,直接返回
if (formerStarOffset == -1) {
return result;
}
// 匹配失败,直接返回
if (!result) {
return false;
}
if (!isEmpty(prefixPattern)) {
remainingURI = substringAfter(str, prefixPattern);
}
// 匹配后缀Pattern
latterStarOffset = indexOf(pattern, START, formerStarOffset + 1);
suffixPattern = substring(pattern, formerStarOffset + 1, latterStarOffset > -1 ? latterStarOffset : pattern.length());
result = remainingURI.contains(suffixPattern);
// 匹配失败,直接返回
if (!result) {
return false;
}
if (!isEmpty(suffixPattern)) {
remainingURI = substringAfter(str, suffixPattern);
}
// 移动指针
beginOffset = latterStarOffset + 1;
}
while (!isEmpty(suffixPattern) && !isEmpty(remainingURI));
return true;
}
@SuppressWarnings("unchecked")
public static <T> T cast(Object obj) {
return (T) obj;
}
}

128
src/main/java/com/yxt/ss/gateway/api/rest/ApiTestRest.java

@ -0,0 +1,128 @@
package com.yxt.ss.gateway.api.rest;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yxt.ss.gateway.api.authutils.StringUtils;
import com.yxt.ss.gateway.api.service.ClientService;
import com.yxt.ss.gateway.api.utils.AppKeyConfig;
import com.yxt.ss.gateway.api.utils.ResultBean;
import com.yxt.ss.gateway.api.utils.SignatureQuery;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
/**
* @description:
* @author: dimengzhe
* @date: 2024/12/10
**/
@RestController
@RequestMapping("/ApiTestRest")
public class ApiTestRest {
@Autowired
private ClientService clientService;
//appkey
static final String APPKEY = "appKey4";
static final String SECRET = "secret";
//开发端,生成签名并调用服务器端验证签名、appKey等值。
@PostMapping("/getSign")
ResultBean getSign(SignatureQuery query) {
ResultBean<String> rb = ResultBean.fireFail();
try {
Map<String, String> formData = query.getParameters();
//使用treeMap排序
Map<String, String> tree = new TreeMap<>(formData);
tree.put("_app", APPKEY);
tree.put("_t", String.valueOf(System.currentTimeMillis() / 1000));
tree.put("_s", "");
// 生成签名
String sign = clientService.generateSignature(tree, SECRET);
//添加签名值map
tree.put("_sign", sign);
//发起请求
ResultBean resultBean = client(tree);
if (!resultBean.getSuccess()) {
return rb.setMsg(resultBean.getMsg());
}
//通过验证继续调用接口
return rb.success();
} catch (UnsupportedEncodingException e) {
return rb.setMsg("Unsupported encoding: " + e.getMessage());
} catch (NoSuchAlgorithmException e) {
return rb.setMsg("Algorithm not found: " + e.getMessage());
}
}
//发起请求验证签名等
public ResultBean client(Map<String, String> data) {
ResultBean rb = ResultBean.fireFail();
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
try {
// 构建URL
String endPoint = "http://127.0.0.1:9999";
String path = "/signature/validate";
// 创建FormData
FormBody.Builder formBuilder = new FormBody.Builder();
for (Map.Entry<String, String> entry : data.entrySet()) {
formBuilder.add(entry.getKey(), entry.getValue());
}
RequestBody formBody = formBuilder.build();
// 构建POST请求
String url = endPoint + path;
System.out.println("Request URL: " + url);
System.out.println("Request Data: " + data);
Request request = new Request.Builder()
.url(url)
.post(formBody)
.build();
// 发送请求
try (Response response = client.newCall(request).execute()) {
String responseBody = response.body().string();
// 使用 Jackson 解析 JSON 响应
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(responseBody);
String success = jsonNode.path("success").asText();
String msg = jsonNode.path("msg").asText();
if ("false".equals(success)) {
return rb.setMsg(msg);
}
if (response.isSuccessful()) {
System.out.println("Response: " + response.body().string());
} else {
System.err.println("Request failed: " + response.message());
}
}
} catch (IOException e) {
System.err.println("Network error: " + e.getMessage());
} catch (Exception e) {
System.err.println("Unexpected error: " + e.getMessage());
}
return rb.success();
}
}

6
src/main/java/com/yxt/ss/gateway/api/rest/ClientRest.java

@ -1,3 +1,4 @@
/*
package com.yxt.ss.gateway.api.rest;
import com.fasterxml.jackson.databind.JsonNode;
@ -18,11 +19,13 @@ import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
*/
/**
* @description:
* @author: dimengzhe
* @date: 2024/12/10
**/
**//*
@RestController
@RequestMapping("/client")
public class ClientRest {
@ -123,3 +126,4 @@ public class ClientRest {
return rb.success();
}
}
*/

8
src/main/java/com/yxt/ss/gateway/api/service/ClientService.java

@ -9,6 +9,7 @@ import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
/**
* @description:
@ -31,13 +32,12 @@ public class ClientService {
* @param parameters 请求参数
* @return 签名
*/
public String generateSignature(Map<String, String> parameters) throws UnsupportedEncodingException, NoSuchAlgorithmException {
public String generateSignature(Map<String, String> parameters, String secret) throws UnsupportedEncodingException, NoSuchAlgorithmException {
//1.对参数进行排序
Map<String, String> treeMap = new TreeMap<>(parameters);
// 2. 拼接参数字符串
String content = joinParameters(parameters);
// 3. 将密钥加在参数字符串的前后
String secret = getSecret(parameters.get("_app"));
content = secret + content + secret;
// 4. 计算签名 (MD5)

63
src/main/java/com/yxt/ss/gateway/api/service/Signature.java

@ -34,62 +34,41 @@ public class Signature {
@PostMapping("/validate")
ResultBean validate(Map<String, String> data) {
ResultBean rb = ResultBean.fireFail();
Map<String, String> parameters = data;
//1、解析参数,校验_app是否正确,_t是否在5分钟内。
//检验_app 参数,1参数中是否存在2该参数是否为空3_app参数值是否在数据库中存在
if (!parameters.containsKey("_app") ||
parameters.get("_app") == null ||
parameters.get("_app").trim().isEmpty()) {
// 解析参数:_app是否存在、_app参数值是否在数据库中存在
String app = data.get("_app");
if (StringUtils.isEmpty(app)) {
return rb.setMsg("_app参数缺失或无效");
}
//2、根据_app参数获取对应的secret值。后续考虑从数据库中获取。
String secret = getSecret(parameters.get("_app"));
// 获取 secret 值:根据_app参数获取对应的secret值。后续考虑从数据库中获取。
String secret = getSecret(app);
if (StringUtils.isEmpty(secret)) {
return rb.setMsg("_app参数不正确");
}
if (parameters.containsKey("_t") ||
parameters.get("_t") == null ||
parameters.get("_t").trim().isEmpty()) {
// 校验时间戳 _t 参数
String timestampStr = data.get("_t");
if (StringUtils.isEmpty(timestampStr)) {
return rb.setMsg("_t参数缺失");
}
String _t = parameters.get("_t");
// 获取当前的秒级时间戳
long timestamp;
try {
timestamp = Long.parseLong(timestampStr);
} catch (NumberFormatException e) {
return rb.setMsg("_t参数格式不正确");
}
// 时间范围校验
long currentTimestamp = Instant.now().getEpochSecond();
// 将字符串转换为 long 类型
long timestamp = Long.parseLong(_t);
// 计算时间差,允许最大偏差 5 分钟(300秒)
long timeDifference = Math.abs(currentTimestamp - timestamp);
if (timeDifference > 300) {
final int ALLOWED_TIME_DIFF = 300; // 最大允许时间偏差(秒)
if (timeDifference > ALLOWED_TIME_DIFF) {
return rb.setMsg("时间已超过5分钟,时间失效");
}
String _sign = parameters.get("_sign");
//3、检验签名,成功则继续调用接口,失败返回失败信息。
parameters.remove("_sign");
try {
// 3.1. 重新生成签名
String calculatedSignature = SignatureUtil.generateSignature(parameters, secret);
// 3.2. 使用固定时间比较方式验证签名
boolean valid = MessageDigest.isEqual(
calculatedSignature.getBytes("UTF-8"),
_sign.getBytes("UTF-8")
);
if (!valid) {
return rb.setMsg("签名不正确");
}
} catch (UnsupportedEncodingException e) {
return rb.setMsg("Encoding error: " + e.getMessage());
} catch (NoSuchAlgorithmException e) {
return rb.setMsg("Algorithm error: " + e.getMessage());
} catch (Exception e) {
return rb.setMsg("Unexpected error: " + e.getMessage());
// 签名验证
ResultBean<Boolean> resultBean = SignatureUtil.validateSignature(data, secret);
if (!resultBean.getSuccess()) {
return rb.setMsg(resultBean.getMsg());
}
return rb.success();

4
src/main/java/com/yxt/ss/gateway/api/utils/AppKeyConfig.java

@ -3,6 +3,8 @@ package com.yxt.ss.gateway.api.utils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
/**
@ -11,7 +13,7 @@ import java.util.Map;
* @date: 2024/12/9
**/
@Component
@ConfigurationProperties(prefix = "app.keys")
@ConfigurationProperties(prefix = "appkey")
public class AppKeyConfig {
private Map<String, String> keys;

40
src/main/java/com/yxt/ss/gateway/api/utils/SignatureUtil.java

@ -1,5 +1,8 @@
package com.yxt.ss.gateway.api.utils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
@ -68,4 +71,41 @@ public class SignatureUtil {
}
return sb.toString();
}
/**
* 验证签名是否正确
*
* @param parameters 请求参数
* @param secret 密钥
* @return 是否验证通过
*/
public static ResultBean<Boolean> validateSignature(Map<String, String> parameters, String secret) {
ResultBean<Boolean> rb = ResultBean.fireFail();
boolean valid = false;
//原签名
String _sign = parameters.get("_sign");
//3、检验签名,成功则继续调用接口,失败返回失败信息。
parameters.remove("_sign");
try {
// 3.1. 重新生成签名
String calculatedSignature = SignatureUtil.generateSignature(parameters, secret);
// 3.2. 使用固定时间比较方式验证签名
valid = MessageDigest.isEqual(
calculatedSignature.getBytes("UTF-8"),
_sign.getBytes("UTF-8")
);
if (!valid) {
return rb.setMsg("签名不正确");
}
} catch (UnsupportedEncodingException e) {
return rb.setMsg("Encoding error: " + e.getMessage());
} catch (NoSuchAlgorithmException e) {
return rb.setMsg("Algorithm error: " + e.getMessage());
} catch (Exception e) {
return rb.setMsg("Unexpected error: " + e.getMessage());
}
return rb.success().setData(valid);
}
}

29
src/main/java/com/yxt/ss/gateway/api/utils/WebFluxLoggingConfig.java

@ -0,0 +1,29 @@
package com.yxt.ss.gateway.api.utils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.WebFilter;
/**
* @description:
* @author: dimengzhe
* @date: 2024/12/11
**/
@Configuration
public class WebFluxLoggingConfig {
@Bean
public WebFilter loggingFilter() {
return (exchange, chain) -> {
// 打印请求信息
System.out.println("Request: " + exchange.getRequest().getURI());
// 继续处理请求
return chain.filter(exchange)
.doOnTerminate(() -> {
// 打印响应信息
System.out.println("Response: " + exchange.getResponse().getStatusCode());
});
};
}
}

2
src/main/resources/application-devv.yml

@ -1,4 +1,4 @@
app:
appkey:
keys:
appKey1: secret1
appKey2: secret2

21
src/main/resources/application.yml

@ -5,10 +5,25 @@ spring:
name: ss-gateway-api
profiles:
active: devv
cloud:
gateway:
httpclient:
response-timeout: 10s
connect-timeout: 30
routes:
- id: ss-gateway-api
predicates:
- Path= /ssa/**
uri: lb://ss-gateway-api
filters:
- StripPrefix=1
ignore:
whites:
whitesTwo: #包含所有
logging:
level:
org:
springframework:
context: DEBUG
org.springframework.web.filter.RequestLoggingFilter: DEBUG

Loading…
Cancel
Save