Merge branch 'v6-dev' of gitee.com:dromara/hutool into v6-dev

This commit is contained in:
Looly 2023-03-09 11:59:58 +08:00
commit a5b1e4a76a
11 changed files with 172 additions and 322 deletions

View File

@ -1,30 +1,18 @@
package cn.hutool.core.date;
import cn.hutool.core.date.format.GlobalCustomFormat;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ObjUtil;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.*;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.ChronoLocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.TemporalUnit;
import java.time.temporal.WeekFields;
import java.time.temporal.*;
import java.util.Date;
import java.util.TimeZone;
import java.util.function.Function;
/**
* JDK8+中的{@link java.time} 工具类封装
@ -355,6 +343,17 @@ public class TimeUtil extends TemporalAccessorUtil {
return format(date, DatePattern.NORM_DATE_FORMATTER);
}
/**
* 格式化时间函数
*
* @param dateTimeFormatter {@link DateTimeFormatter}
* @return 格式化时间的函数
*/
public static Function<TemporalAccessor, String> formatFunc(final DateTimeFormatter dateTimeFormatter) {
return LambdaUtil.toFunction(TimeUtil::format, dateTimeFormatter);
}
/**
* 日期偏移,根据field不同加不同值偏移会修改传入的对象
*

View File

@ -13,8 +13,7 @@ import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.*;
/**
* Lambda相关工具类
@ -34,12 +33,12 @@ public class LambdaUtil {
* MyTeacher myTeacher = new MyTeacher();
* Class<MyTeacher> supplierClass = LambdaUtil.getRealClass(myTeacher::getAge);
* Assert.assertEquals(MyTeacher.class, supplierClass);
* }</pre>
* }</pre>
* </li>
* <li>引用静态无参方法<pre>{@code
* Class<MyTeacher> staticSupplierClass = LambdaUtil.getRealClass(MyTeacher::takeAge);
* Assert.assertEquals(MyTeacher.class, staticSupplierClass);
* }</pre>
* }</pre>
* </li>
* </ul>
* 在以下场景无法获取到正确类型
@ -123,7 +122,7 @@ public class LambdaUtil {
* </ul>
*
* @param func 函数
* @param <T> lambda的类型
* @param <T> lambda的类型
* @return 方法名称
* @throws IllegalArgumentException 非Getter或Setter方法
* @since 5.7.23
@ -136,8 +135,8 @@ public class LambdaUtil {
* 等效于 Obj::getXxx
*
* @param getMethod getter方法
* @param <T> 调用getter方法对象类型
* @param <R> getter方法返回值类型
* @param <T> 调用getter方法对象类型
* @param <R> getter方法返回值类型
* @return Obj::getXxx
*/
@SuppressWarnings("unchecked")
@ -148,10 +147,10 @@ public class LambdaUtil {
/**
* 等效于 Obj::getXxx
*
* @param clazz 调用getter方法对象类
* @param clazz 调用getter方法对象类
* @param fieldName 字段名称
* @param <T> 调用getter方法对象类型
* @param <R> getter方法返回值类型
* @param <T> 调用getter方法对象类型
* @param <R> getter方法返回值类型
* @return Obj::getXxx
*/
@SuppressWarnings("unchecked")
@ -163,8 +162,8 @@ public class LambdaUtil {
* 等效于 Obj::setXxx
*
* @param setMethod setter方法
* @param <T> 调用setter方法对象类型
* @param <P> setter方法返回的值类型
* @param <T> 调用setter方法对象类型
* @param <P> setter方法返回的值类型
* @return Obj::setXxx
*/
@SuppressWarnings("unchecked")
@ -175,10 +174,10 @@ public class LambdaUtil {
/**
* Obj::setXxx
*
* @param clazz 调用setter方法对象类
* @param clazz 调用setter方法对象类
* @param fieldName 字段名称
* @param <T> 调用setter方法对象类型
* @param <P> setter方法返回的值类型
* @param <T> 调用setter方法对象类型
* @param <P> setter方法返回的值类型
* @return Obj::setXxx
*/
@SuppressWarnings("unchecked")
@ -189,17 +188,60 @@ public class LambdaUtil {
/**
* 等效于 Obj::method
*
* @param lambdaType 接受lambda的函数式接口类型
* @param clazz 调用类
* @param methodName 方法名
* @param lambdaType 接受lambda的函数式接口类型
* @param clazz 调用类
* @param methodName 方法名
* @param paramsTypes 方法参数类型数组
* @param <F> 函数式接口类型
* @param <F> 函数式接口类型
* @return Obj::method
*/
public static <F> F build(final Class<F> lambdaType, final Class<?> clazz, final String methodName, final Class<?>... paramsTypes) {
return LambdaFactory.build(lambdaType, clazz, methodName, paramsTypes);
}
/**
* 通过自定义固定参数{@link BiFunction}转换为{@link Function}
*
* @param biFunction {@link BiFunction}
* @param param 参数
* @param <T> 参数类型
* @param <U> 参数2类型
* @param <R> 返回值类型
* @return {@link Function}
* @since 6.0.0
*/
public static <T, U, R> Function<T, R> toFunction(final BiFunction<T, U, R> biFunction, final U param) {
return (t) -> biFunction.apply(t, param);
}
/**
* 通过自定义固定参数{@link BiPredicate}转换为{@link Predicate}
*
* @param biPredicate {@link BiFunction}
* @param param 参数
* @param <T> 参数类型
* @param <U> 参数2类型
* @return {@link Predicate}
* @since 6.0.0
*/
public static <T, U> Predicate<T> toPredicate(final BiPredicate<T, U> biPredicate, final U param) {
return (t) -> biPredicate.test(t, param);
}
/**
* 通过自定义固定参数{@link BiConsumer}转换为{@link Consumer}
*
* @param biConsumer {@link BiConsumer}
* @param param 参数
* @param <T> 参数类型
* @param <U> 参数2类型
* @return {@link Consumer}
* @since 6.0.0
*/
public static <T, U> Consumer<T> toPredicate(final BiConsumer<T, U> biConsumer, final U param) {
return (t) -> biConsumer.accept(t, param);
}
//region Private methods
/**

View File

@ -12,6 +12,13 @@ import java.util.function.Predicate;
*/
public class PredicateUtil {
/**
* 反向条件
*
* @param predicate 条件
* @param <T> 参数类型
* @return 反向条件 {@link Predicate}
*/
public static <T> Predicate<T> negate(final Predicate<T> predicate) {
return predicate.negate();
}

View File

@ -10,9 +10,9 @@ import java.util.function.BinaryOperator;
/**
* SerBinaryOperator
*
* @author VampireAchao
* @since 2022/6/8
* @param <T> 参数和返回值类型
* @author VampireAchao
* @see BinaryOperator
*/
@FunctionalInterface
public interface SerBinaryOperator<T> extends BinaryOperator<T>, Serializable {
@ -44,7 +44,7 @@ public interface SerBinaryOperator<T> extends BinaryOperator<T>, Serializable {
}
/**
* Returns a {@link SerBinaryOperator} which returns the lesser of two elements
* Returns a {@code SerBinaryOperator} which returns the lesser of two elements
* according to the specified {@code Comparator}.
*
* @param <T> the type of the input arguments of the comparator
@ -59,7 +59,7 @@ public interface SerBinaryOperator<T> extends BinaryOperator<T>, Serializable {
}
/**
* Returns a {@link SerBinaryOperator} which returns the greater of two elements
* Returns a {@code SerBinaryOperator} which returns the greater of two elements
* according to the specified {@code Comparator}.
*
* @param <T> the type of the input arguments of the comparator

View File

@ -66,7 +66,7 @@ public interface SerConsumer<T> extends Consumer<T>, Serializable {
*/
default SerConsumer<T> andThen(final SerConsumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> {
return (final T t) -> {
accept(t);
after.accept(t);
};

View File

@ -34,10 +34,10 @@ public interface SerConsumer3<P1, P2, P3> extends Serializable {
* @param p2 参数二
* @param p3 参数三
*/
default void accept(P1 p1, P2 p2, P3 p3) {
default void accept(final P1 p1, final P2 p2, final P3 p3) {
try {
accepting(p1, p2, p3);
} catch (Exception e) {
} catch (final Exception e) {
throw new UtilException(e);
}
}
@ -54,9 +54,9 @@ public interface SerConsumer3<P1, P2, P3> extends Serializable {
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default SerConsumer3<P1, P2, P3> andThen(SerConsumer3<P1, P2, P3> after) {
default SerConsumer3<P1, P2, P3> andThen(final SerConsumer3<P1, P2, P3> after) {
Objects.requireNonNull(after);
return (P1 p1, P2 p2, P3 p3) -> {
return (final P1 p1, final P2 p2, final P3 p3) -> {
accept(p1, p2, p3);
after.accept(p1, p2, p3);
};

View File

@ -10,7 +10,10 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TimeUtilTest {
@ -319,4 +322,25 @@ public class TimeUtilTest {
Assert.assertThrows(IllegalArgumentException.class, () -> TimeUtil.isIn(begin, null, end, false, false));
Assert.assertThrows(IllegalArgumentException.class, () -> TimeUtil.isIn(begin, begin, null, false, false));
}
@Test
public void formatDateFunctionTest() {
final List<String> dateStrList = Stream.of("2023-03-01", "2023-03-02")
.map(LocalDate::parse)
.map(TimeUtil.formatFunc(DatePattern.CHINESE_DATE_FORMATTER))
.collect(Collectors.toList());
Assert.assertEquals("2023年03月01日", dateStrList.get(0));
Assert.assertEquals("2023年03月02日", dateStrList.get(1));
}
@Test
public void formatTimeFunctionTest() {
final List<String> dateStrList = Stream.of("2023-03-01T12:23:56", "2023-03-02T12:23:56")
.map(LocalDateTime::parse)
.map(TimeUtil.formatFunc(DatePattern.CHINESE_DATE_FORMATTER))
.collect(Collectors.toList());
Assert.assertEquals("2023年03月01日", dateStrList.get(0));
Assert.assertEquals("2023年03月02日", dateStrList.get(1));
}
}

View File

@ -282,4 +282,17 @@ public class LambdaUtilTest {
REF_invokeSpecial,
REF_newInvokeSpecial,
}
@Test
public void lambdaClassNameTest() {
final String lambdaClassName1 = LambdaUtilTestHelper.getLambdaClassName(MyTeacher::getAge);
final String lambdaClassName2 = LambdaUtilTestHelper.getLambdaClassName(MyTeacher::getAge);
Assert.assertNotEquals(lambdaClassName1, lambdaClassName2);
}
static class LambdaUtilTestHelper {
public static <P> String getLambdaClassName(final Function<P, ?> func) {
return func.getClass().getName();
}
}
}

View File

@ -1,278 +0,0 @@
package cn.hutool.http;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.net.ssl.SSLUtil;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetSocketAddress;
import java.net.Proxy;
/**
* Http配置项
*
* @author looly
* @since 5.8.0
*/
public class HttpConfig {
/**
* 创建默认Http配置信息
*
* @return HttpConfig
*/
public static HttpConfig of() {
return new HttpConfig();
}
/**
* 默认连接超时
*/
public int connectionTimeout = HttpGlobalConfig.getTimeout();
/**
* 默认读取超时
*/
public int readTimeout = HttpGlobalConfig.getTimeout();
/**
* 是否禁用缓存
*/
public boolean isDisableCache;
/**
* 最大重定向次数
*/
public int maxRedirectCount = HttpGlobalConfig.getMaxRedirectCount();
/**
* 代理
*/
public Proxy proxy;
/**
* HostnameVerifier用于HTTPS安全连接
*/
public HostnameVerifier hostnameVerifier;
/**
* SSLSocketFactory用于HTTPS安全连接
*/
public SSLSocketFactory ssf;
/**
* Chuncked块大小0或小于0表示不设置Chuncked模式
*/
public int blockSize;
/**
* 获取是否忽略响应读取时可能的EOF异常<br>
* 在Http协议中对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束<br>
* 如果服务端未遵循这个规范或响应没有正常结束会报EOF异常此选项用于是否忽略这个异常
*/
boolean ignoreEOFError = HttpGlobalConfig.isIgnoreEOFError();
/**
* 获取是否忽略解码URL包括URL中的Path部分和Param部分<br>
* 在构建Http请求时用户传入的URL可能有编码后和未编码的内容混合在一起如果此参数为{@code true}则会统一解码编码后的参数<br>
* 按照RFC3986规范在发送请求时全部编码之如果为{@code false}则不会解码已经编码的内容在请求时只编码需要编码的部分
*/
boolean decodeUrl = HttpGlobalConfig.isDecodeUrl();
/**
* 重定向时是否使用拦截器
*/
public boolean interceptorOnRedirect;
/**
* 设置超时单位毫秒<br>
* 超时包括
*
* <pre>
* 1. 连接超时
* 2. 读取响应超时
* </pre>
*
* @param milliseconds 超时毫秒数
* @return this
* @see #setConnectionTimeout(int)
* @see #setReadTimeout(int)
*/
public HttpConfig timeout(final int milliseconds) {
setConnectionTimeout(milliseconds);
setReadTimeout(milliseconds);
return this;
}
/**
* 设置连接超时单位毫秒
*
* @param milliseconds 超时毫秒数
* @return this
*/
public HttpConfig setConnectionTimeout(final int milliseconds) {
this.connectionTimeout = milliseconds;
return this;
}
/**
* 设置读取超时单位毫秒
*
* @param milliseconds 超时毫秒数
* @return this
*/
public HttpConfig setReadTimeout(final int milliseconds) {
this.readTimeout = milliseconds;
return this;
}
/**
* 禁用缓存
*
* @return this
*/
public HttpConfig disableCache() {
this.isDisableCache = true;
return this;
}
/**
* 设置最大重定向次数<br>
* 如果次数小于1则表示不重定向大于等于1表示打开重定向
*
* @param maxRedirectCount 最大重定向次数
* @return this
*/
public HttpConfig setMaxRedirectCount(final int maxRedirectCount) {
this.maxRedirectCount = Math.max(maxRedirectCount, 0);
return this;
}
/**
* 设置域名验证器<br>
* 只针对HTTPS请求如果不设置不做验证所有域名被信任
*
* @param hostnameVerifier HostnameVerifier
* @return this
*/
public HttpConfig setHostnameVerifier(final HostnameVerifier hostnameVerifier) {
// 验证域
this.hostnameVerifier = hostnameVerifier;
return this;
}
/**
* 设置Http代理
*
* @param host 代理 主机
* @param port 代理 端口
* @return this
*/
public HttpConfig setHttpProxy(final String host, final int port) {
final Proxy proxy = new Proxy(Proxy.Type.HTTP,
new InetSocketAddress(host, port));
return setProxy(proxy);
}
/**
* 设置代理
*
* @param proxy 代理 {@link Proxy}
* @return this
*/
public HttpConfig setProxy(final Proxy proxy) {
this.proxy = proxy;
return this;
}
/**
* 设置SSLSocketFactory<br>
* 只针对HTTPS请求如果不设置使用默认的SSLSocketFactory<br>
* 默认SSLSocketFactory为SSLSocketFactoryBuilder.create().build();
*
* @param ssf SSLScketFactory
* @return this
*/
public HttpConfig setSSLSocketFactory(final SSLSocketFactory ssf) {
this.ssf = ssf;
return this;
}
/**
* 设置HTTPS安全连接协议只针对HTTPS请求可以使用的协议包括<br>
* 此方法调用后{@link #setSSLSocketFactory(SSLSocketFactory)} 将被覆盖
*
* <pre>
* 1. TLSv1.2
* 2. TLSv1.1
* 3. SSLv3
* ...
* </pre>
*
* @param protocol 协议
* @return this
* @see SSLUtil#createSSLContext(String)
* @see #setSSLSocketFactory(SSLSocketFactory)
*/
public HttpConfig setSSLProtocol(final String protocol) {
Assert.notBlank(protocol, "protocol must be not blank!");
setSSLSocketFactory(SSLUtil.createSSLContext(protocol).getSocketFactory());
return this;
}
/**
* 采用流方式上传数据无需本地缓存数据<br>
* HttpUrlConnection默认是将所有数据读到本地缓存然后再发送给服务器这样上传大文件时就会导致内存溢出
*
* @param blockSize 块大小bytes数0或小于0表示不设置Chuncked模式
* @return this
*/
public HttpConfig setBlockSize(final int blockSize) {
this.blockSize = blockSize;
return this;
}
/**
* 设置是否忽略响应读取时可能的EOF异常<br>
* 在Http协议中对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束<br>
* 如果服务端未遵循这个规范或响应没有正常结束会报EOF异常此选项用于是否忽略这个异常
*
* @param ignoreEOFError 是否忽略响应读取时可能的EOF异常
* @return this
* @since 5.7.20
*/
public HttpConfig setIgnoreEOFError(final boolean ignoreEOFError) {
this.ignoreEOFError = ignoreEOFError;
return this;
}
/**
* 设置是否忽略解码URL包括URL中的Path部分和Param部分<br>
* 在构建Http请求时用户传入的URL可能有编码后和未编码的内容混合在一起如果此参数为{@code true}则会统一解码编码后的参数<br>
* 按照RFC3986规范在发送请求时全部编码之如果为{@code false}则不会解码已经编码的内容在请求时只编码需要编码的部分
*
* @param decodeUrl 是否忽略解码URL
* @return this
*/
public HttpConfig setDecodeUrl(final boolean decodeUrl) {
this.decodeUrl = decodeUrl;
return this;
}
/**
* 重定向时是否使用拦截器
*
* @param interceptorOnRedirect 重定向时是否使用拦截器
* @return this
*/
public HttpConfig setInterceptorOnRedirect(final boolean interceptorOnRedirect) {
this.interceptorOnRedirect = interceptorOnRedirect;
return this;
}
/**
* 获取是否忽略响应读取时可能的EOF异常
*
* @return 是否忽略响应读取时可能的EOF异常
*/
public boolean isIgnoreEOFError() {
return ignoreEOFError;
}
}

View File

@ -0,0 +1,27 @@
package cn.hutool.http.server;
import cn.hutool.core.lang.Console;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.meta.Header;
public class RedirectServerTest {
public static void main(final String[] args) {
HttpUtil.createServer(8888).addAction("/redirect1", (request, response) -> {
response.addHeader(Header.LOCATION.getValue(),"http://localhost:8888/redirect2");
response.addHeader(Header.SET_COOKIE.getValue(),"redirect1=1; path=/; HttpOnly");
response.send(301);
}).addAction("/redirect2", (request, response) -> {
response.addHeader(Header.LOCATION.getValue(),"http://localhost:8888/redirect3");
response.addHeader(Header.SET_COOKIE.getValue(), "redirect2=2; path=/; HttpOnly");
response.send(301);
}).addAction("/redirect3", (request, response) -> {
response.addHeader(Header.LOCATION.getValue(),"http://localhost:8888/redirect4");
response.addHeader(Header.SET_COOKIE.getValue(),"redirect3=3; path=/; HttpOnly");
response.send(301);
}).addAction("/redirect4", (request, response) -> {
final String cookie = request.getHeader(Header.COOKIE);
Console.log(cookie);
response.sendOk();
}).start();
}
}

View File

@ -172,4 +172,20 @@ public class JWTTest {
final Object test2 = JWT.of(sign).getPayload().getClaim("test2");
Assert.assertEquals(Long.class, test2.getClass());
}
@Test
public void getLongTest(){
final String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"
+ ".eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJhZG1pbiIsImRldmljZSI6ImRlZmF1bHQtZGV2aWNlIiwiZWZmIjoxNjc4Mjg1NzEzOTM1LCJyblN0ciI6IkVuMTczWFhvWUNaaVZUWFNGOTNsN1pabGtOalNTd0pmIn0"
+ ".wRe2soTaWYPhwcjxdzesDi1BgEm9D61K-mMT3fPc4YM"
+ "";
final JWT jwt = JWTUtil.parseToken(rightToken);
Assert.assertEquals(
"{\"loginType\":\"login\",\"loginId\":\"admin\",\"device\":\"default-device\"," +
"\"eff\":1678285713935,\"rnStr\":\"En173XXoYCZiVTXSF93l7ZZlkNjSSwJf\"}",
jwt.getPayloads().toString());
Assert.assertEquals(Long.valueOf(1678285713935L), jwt.getPayloads().getLong("eff"));
}
}