Prepare release

This commit is contained in:
Looly 2020-12-16 00:02:48 +08:00
commit 987c3fab4f
132 changed files with 7059 additions and 5487 deletions

View File

@ -3,6 +3,43 @@
-------------------------------------------------------------------------------------------------------------
# 5.5.3 (2020-12-11)
### 新特性
* 【core 】 IdcardUtil增加行政区划83issue#1277@Github
* 【core 】 multipart中int改为long解决大文件上传越界问题issue#I27WZ3@Gitee
* 【core 】 ListUtil.page增加检查pr#224@Gitee
* 【db 】 Db增加使用sql的page方法issue#247@Gitee
* 【cache 】 CacheObj的isExpired()逻辑修改issue#1295@Github
* 【json 】 JSONStrFormater改为JSONStrFormatter
* 【dfa 】 增加FoundWordpr#1290@Github
* 【core 】 增加Segmentpr#1290@Github
* 【core 】 增加CharSequenceUtil
* 【poi 】 Excel07SaxReader拆分出SheetDataSaxHandler
* 【core 】 CollUtil.addAll增加判空pr#228@Gitee
* 【core 】 修正DateUtil.betweenXXX注释错误issue#I28XGW@Gitee
* 【core 】 增加NioUtil
* 【core 】 增加GanymedUtil
* 【poi 】 增加OFD支持OfdWriter
* 【poi 】 修复NumberUtil属性拼写错误pr#1311@Github
* 【core 】 MapUtil增加getQuietly方法issue#I29IWO@Gitee
### Bug修复
* 【cache 】 修复Cache中get重复misCount计数问题issue#1281@Github
* 【poi 】 修复sax读取自定义格式单元格无法识别日期类型的问题issue#1283@Github
* 【core 】 修复CollUtil.get越界问题issue#1292@Github
* 【core 】 修复TemporalAccessorUtil无法格式化LocalDate带时间问题issue#1289@Github
* 【json 】 修复自定义日期格式的LocalDateTime没有包装引号问题issue#1289@Github
* 【cache 】 get中unlock改为unlockReadissue#1294@Github
* 【db 】 修复表名包含点导致的问题issue#1300@Github
* 【poi 】 修复xdr:row标签导致的问题issue#1297@Github
* 【core 】 修复FileUtil.loopFiles使用FileFilter无效问题issue#I28V48@Gitee
* 【extra 】 修复JschUtil.execByShell返回空的问题issue#1067@Github
* 【poi 】 修复特殊的excel使用sax读取时未读到值的问题issue#1303@Github
* 【http 】 修复HttpUtil类条件判断错误pr#232@Gitee
-------------------------------------------------------------------------------------------------------------
# 5.5.2 (2020-12-01)
### 新特性
@ -40,7 +77,7 @@
* 【core 】 修复HexUtil.format问题issue#I268XT@Gitee
* 【core 】 修复ZipUtil判断压缩文件是否位于压缩目录内的逻辑有误的问题issue#1251@Github
* 【json 】 修复JSONObject.accumulate问题
* 【core 】 修复部分xlsx文件sax方式解析空指针问题issue#1265@Github
* 【poi 】 修复部分xlsx文件sax方式解析空指针问题issue#1265@Github
* 【core 】 修复PatternPool中邮编的正则issue#1274@Github
-------------------------------------------------------------------------------------------------------------

View File

@ -125,19 +125,19 @@ Each module can be introduced individually, or all modules can be introduced by
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</dependency>
```
### Gradle
```
compile 'cn.hutool:hutool-all:5.5.2'
compile 'cn.hutool:hutool-all:5.5.3'
```
## Download
- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.2/)
- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.2/)
- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.3/)
- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.3/)
> note:
> Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available.

View File

@ -123,21 +123,21 @@ Hutool的存在就是为了减少代码搜索成本避免网络上参差不
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</dependency>
```
### Gradle
```
compile 'cn.hutool:hutool-all:5.5.2'
compile 'cn.hutool:hutool-all:5.5.3'
```
### 非Maven项目
点击以下任一链接,下载`hutool-all-X.X.X.jar`即可:
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.2/)
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.2/)
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.3/)
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.3/)
> 注意
> Hutool 5.x支持JDK8+对Android平台没有测试不能保证所有工具类或工具方法可用。

View File

@ -1 +1 @@
5.5.2
5.5.3

View File

@ -1 +1 @@
var version = '5.5.2'
var version = '5.5.3'

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</parent>
<artifactId>hutool-all</artifactId>

View File

@ -45,6 +45,7 @@ public class Hutool {
/**
* 显示Hutool所有的工具类
*
* @return 工具类名集合
* @since 5.5.2
*/
public static Set<Class<?>> getAllUtils() {

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</parent>
<artifactId>hutool-aop</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</parent>
<artifactId>hutool-bloomFilter</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</parent>
<artifactId>hutool-bom</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</parent>
<artifactId>hutool-cache</artifactId>

View File

@ -15,7 +15,7 @@ import java.util.concurrent.locks.StampedLock;
* 继承此抽象缓存需要<br>
* <ul>
* <li>创建一个新的Map</li>
* <li>实现 <code>prune</code> 策略</li>
* <li>实现 {@code prune} 策略</li>
* </ul>
*
* @param <K> 键类型
@ -30,11 +30,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
private final StampedLock lock = new StampedLock();
/**
* 返回缓存容量<code>0</code>表示无大小限制
* 返回缓存容量{@code 0}表示无大小限制
*/
protected int capacity;
/**
* 缓存失效时长 <code>0</code> 表示无限制单位毫秒
* 缓存失效时长 {@code 0} 表示无限制单位毫秒
*/
protected long timeout;
@ -168,15 +168,13 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
return null;
}
if (co.isExpired()) {
missCount.getAndIncrement();
} else {
// 命中
if (false == co.isExpired()) {
hitCount.getAndIncrement();
return co.get(isUpdateLastAccess);
}
} finally {
lock.unlock(stamp);
lock.unlockRead(stamp);
}
// 过期

View File

@ -44,9 +44,8 @@ public class CacheObj<K, V> implements Serializable{
*/
boolean isExpired() {
if(this.ttl > 0) {
final long expiredTime = this.lastAccess + this.ttl;
// expiredTime > 0 杜绝Long类型溢出变负数问题当当前时间超过过期时间表示过期
return expiredTime > 0 && expiredTime < System.currentTimeMillis();
// 此处不考虑时间回拨
return (System.currentTimeMillis() - this.lastAccess) > this.ttl;
}
return false;
}

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</parent>
<artifactId>hutool-captcha</artifactId>

View File

@ -5,11 +5,19 @@
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</parent>
<artifactId>hutool-core</artifactId>

View File

@ -745,11 +745,11 @@ public class BeanUtil {
}
/**
* 判断Bean是否为非空对象非空对象表示本身不为<code>null</code>或者含有非<code>null</code>属性的对象
* 判断Bean是否为非空对象非空对象表示本身不为{@code null}或者含有非{@code null}属性的对象
*
* @param bean Bean对象
* @param ignoreFiledNames 忽略检查的字段名
* @return 是否为空<code>true</code> - / <code>false</code> - 非空
* @return 是否为空{@code true} - / {@code false} - 非空
* @since 5.0.7
*/
public static boolean isNotEmpty(Object bean, String... ignoreFiledNames) {
@ -757,12 +757,12 @@ public class BeanUtil {
}
/**
* 判断Bean是否为空对象空对象表示本身为<code>null</code>或者所有属性都为<code>null</code><br>
* 判断Bean是否为空对象空对象表示本身为{@code null}或者所有属性都为{@code null}<br>
* 此方法不判断static属性
*
* @param bean Bean对象
* @param ignoreFiledNames 忽略检查的字段名
* @return 是否为空<code>true</code> - / <code>false</code> - 非空
* @return 是否为空{@code true} - / {@code false} - 非空
* @since 4.1.10
*/
public static boolean isEmpty(Object bean, String... ignoreFiledNames) {
@ -781,12 +781,12 @@ public class BeanUtil {
}
/**
* 判断Bean是否包含值为<code>null</code>的属性<br>
* 对象本身为<code>null</code>也返回true
* 判断Bean是否包含值为{@code null}的属性<br>
* 对象本身为{@code null}也返回true
*
* @param bean Bean对象
* @param ignoreFiledNames 忽略检查的字段名
* @return 是否包含值为<code>null</code>的属性<code>true</code> - 包含 / <code>false</code> - 不包含
* @return 是否包含值为<code>null</code>的属性{@code true} - 包含 / {@code false} - 不包含
* @since 4.1.10
*/
public static boolean hasNullField(Object bean, String... ignoreFiledNames) {

View File

@ -1870,7 +1870,7 @@ public class CollUtil {
/**
* Iterator转换为Enumeration
* <p>
* Adapt the specified <code>Iterator</code> to the <code>Enumeration</code> interface.
* Adapt the specified {@link Iterator} to the {@link Enumeration} interface.
*
* @param <E> 集合元素类型
* @param iter {@link Iterator}
@ -1883,7 +1883,7 @@ public class CollUtil {
/**
* Enumeration转换为Iterator
* <p>
* Adapt the specified <code>Enumeration</code> to the <code>Iterator</code> interface
* Adapt the specified {@code Enumeration} to the {@code Iterator} interface
*
* @param <E> 集合元素类型
* @param e {@link Enumeration}
@ -2108,6 +2108,9 @@ public class CollUtil {
* @return 原集合
*/
public static <T> Collection<T> addAll(Collection<T> collection, Iterable<T> iterable) {
if (iterable == null) {
return collection;
}
return addAll(collection, iterable.iterator());
}
@ -2186,7 +2189,7 @@ public class CollUtil {
}
// 检查越界
if (index >= size) {
if (index >= size || index < 0) {
return null;
}

View File

@ -245,7 +245,7 @@ public class ListUtil {
int resultSize = list.size();
// 每页条目数大于总数直接返回所有
if (resultSize <= pageSize) {
if (pageNo < (PageUtil.getFirstPageNo()+1)) {
if (pageNo < (PageUtil.getFirstPageNo() + 1)) {
return Collections.unmodifiableList(list);
} else {
// 越界直接返回空
@ -253,7 +253,7 @@ public class ListUtil {
}
}
// 相乘可能会导致越界 临时用long
if (((long) (pageNo-PageUtil.getFirstPageNo()) * pageSize) > resultSize) {
if (((long) (pageNo - PageUtil.getFirstPageNo()) * pageSize) > resultSize) {
// 越界直接返回空
return new ArrayList<>(0);
}
@ -261,6 +261,9 @@ public class ListUtil {
final int[] startEnd = PageUtil.transToStartEnd(pageNo, pageSize);
if (startEnd[1] > resultSize) {
startEnd[1] = resultSize;
if (startEnd[0] > startEnd[1]) {
return empty();
}
}
return list.subList(startEnd[0], startEnd[1]);

View File

@ -49,7 +49,7 @@ public class Convert {
/**
* 转换为字符串<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 如果给定的值为{@code null}或者转换失败返回默认值{@code null}<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -85,7 +85,7 @@ public class Convert {
/**
* 转换为字符<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 如果给定的值为{@code null}或者转换失败返回默认值{@code null}<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -108,7 +108,7 @@ public class Convert {
/**
* 转换为byte<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<br>
* 如果给定的值为{@code null}或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -121,7 +121,7 @@ public class Convert {
/**
* 转换为byte<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 如果给定的值为{@code null}或者转换失败返回默认值{@code null}<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -155,7 +155,7 @@ public class Convert {
/**
* 转换为Short<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<br>
* 如果给定的值为{@code null}或者转换失败返回默认值<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -168,7 +168,7 @@ public class Convert {
/**
* 转换为Short<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 如果给定的值为{@code null}或者转换失败返回默认值{@code null}<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -204,7 +204,7 @@ public class Convert {
/**
* 转换为Number<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 如果给定的值为空或者转换失败返回默认值{@code null}<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -240,7 +240,7 @@ public class Convert {
/**
* 转换为int<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 如果给定的值为{@code null}或者转换失败返回默认值{@code null}<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -275,7 +275,7 @@ public class Convert {
/**
* 转换为long<br>
* 如果给定的值为<code>null</code>或者转换失败返回默认值<code>null</code><br>
* 如果给定的值为{@code null}或者转换失败返回默认值{@code null}<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -310,7 +310,7 @@ public class Convert {
/**
* 转换为double<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 如果给定的值为空或者转换失败返回默认值{@code null}<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -345,7 +345,7 @@ public class Convert {
/**
* 转换为Float<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 如果给定的值为空或者转换失败返回默认值{@code null}<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -380,7 +380,7 @@ public class Convert {
/**
* 转换为boolean<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 如果给定的值为空或者转换失败返回默认值{@code null}<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -415,7 +415,7 @@ public class Convert {
/**
* 转换为BigInteger<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 如果给定的值为空或者转换失败返回默认值{@code null}<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -480,7 +480,7 @@ public class Convert {
/**
* 转换为LocalDateTime<br>
* 如果给定的值为空或者转换失败返回<code>null</code><br>
* 如果给定的值为空或者转换失败返回{@code null}<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -506,7 +506,7 @@ public class Convert {
/**
* 转换为Date<br>
* 如果给定的值为空或者转换失败返回<code>null</code><br>
* 如果给定的值为空或者转换失败返回{@code null}<br>
* 转换失败不会报错
*
* @param value 被转换的值
@ -534,7 +534,7 @@ public class Convert {
/**
* 转换为Enum对象<br>
* 如果给定的值为空或者转换失败返回默认值<code>null</code><br>
* 如果给定的值为空或者转换失败返回默认值{@code null}<br>
*
* @param <E> 枚举类型
* @param clazz Enum的Class

View File

@ -95,7 +95,7 @@ public class DateBetween implements Serializable{
/**
* 计算两个日期相差月数<br>
* 在非重置情况下如果起始日期的天于结束日期的天月数要少算1不足1个月
* 在非重置情况下如果起始日期的天于结束日期的天月数要少算1不足1个月
*
* @param isReset 是否重置时间为起始时间重置天时分秒
* @return 相差月数
@ -122,7 +122,7 @@ public class DateBetween implements Serializable{
/**
* 计算两个日期相差年数<br>
* 在非重置情况下如果起始日期的月于结束日期的月年数要少算1不足1年
* 在非重置情况下如果起始日期的月于结束日期的月年数要少算1不足1年
*
* @param isReset 是否重置时间为起始时间重置月天时分秒
* @return 相差年数

View File

@ -1339,8 +1339,8 @@ public class DateUtil extends CalendarUtil {
* <pre>
* 有时候我们计算相差天数的时候需要忽略时分秒
* 比如2016-02-01 23:59:59和2016-02-02 00:00:00相差一秒
* 如果isReset为<code>false</code>相差天数为0
* 如果isReset为<code>true</code>相差天数将被计算为1
* 如果isReset为{@code false}相差天数为0
* 如果isReset为{@code true}相差天数将被计算为1
* </pre>
*
* @param beginDate 起始日期
@ -1375,7 +1375,7 @@ public class DateUtil extends CalendarUtil {
/**
* 计算两个日期相差月数<br>
* 在非重置情况下如果起始日期的天于结束日期的天月数要少算1不足1个月
* 在非重置情况下如果起始日期的天于结束日期的天月数要少算1不足1个月
*
* @param beginDate 起始日期
* @param endDate 结束日期
@ -1389,7 +1389,7 @@ public class DateUtil extends CalendarUtil {
/**
* 计算两个日期相差年数<br>
* 在非重置情况下如果起始日期的月于结束日期的月年数要少算1不足1年
* 在非重置情况下如果起始日期的月于结束日期的月年数要少算1不足1年
*
* @param beginDate 起始日期
* @param endDate 结束日期

View File

@ -13,6 +13,7 @@ import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalField;
import java.time.temporal.UnsupportedTemporalTypeException;
/**
* {@link TemporalAccessor} 工具类封装
@ -54,7 +55,19 @@ public class TemporalAccessorUtil extends TemporalUtil{
formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
}
try {
return formatter.format(time);
} catch (UnsupportedTemporalTypeException e){
if(time instanceof LocalDate && e.getMessage().contains("HourOfDay")){
// 用户传入LocalDate但是要求格式化带有时间部分转换为LocalDateTime重试
return formatter.format(((LocalDate) time).atStartOfDay());
}else if(time instanceof LocalTime && e.getMessage().contains("YearOfEra")){
// 用户传入LocalTime但是要求格式化带有日期部分转换为LocalDateTime重试
return formatter.format(((LocalTime) time).atDate(LocalDate.now()));
}
throw e;
}
}
/**

View File

@ -1,6 +1,7 @@
package cn.hutool.core.io;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import java.io.IOException;
import java.io.OutputStream;
@ -90,7 +91,7 @@ public class FastByteArrayOutputStream extends OutputStream {
@Override
public String toString() {
return new String(toByteArray());
return toString(CharsetUtil.defaultCharset());
}
/**
@ -104,11 +105,12 @@ public class FastByteArrayOutputStream extends OutputStream {
/**
* 转为字符串
* @param charset 编码
* @param charset 编码,null表示默认编码
* @return 字符串
*/
public String toString(Charset charset) {
return new String(toByteArray(), charset);
return new String(toByteArray(),
ObjectUtil.defaultIfNull(charset, CharsetUtil.defaultCharset()));
}
}

View File

@ -1,7 +1,6 @@
package cn.hutool.core.io;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.file.FileCopier;
import cn.hutool.core.io.file.FileMode;
import cn.hutool.core.io.file.FileNameUtil;
@ -173,13 +172,7 @@ public class FileUtil extends PathUtil {
* @return 文件列表
*/
public static List<File> loopFiles(File file, FileFilter fileFilter) {
if (null == file || false == file.exists()) {
return ListUtil.empty();
}
final List<File> fileList = new ArrayList<>();
walkFiles(file, fileList::add);
return fileList;
return loopFiles(file, -1, fileFilter);
}
/**
@ -216,7 +209,7 @@ public class FileUtil extends PathUtil {
* @return 文件列表
* @since 4.6.3
*/
public static List<File> loopFiles(File file, int maxDepth, final FileFilter fileFilter) {
public static List<File> loopFiles(File file, int maxDepth, FileFilter fileFilter) {
return loopFiles(file.toPath(), maxDepth, fileFilter);
}

View File

@ -28,13 +28,8 @@ import java.io.PushbackReader;
import java.io.Reader;
import java.io.Serializable;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Objects;
@ -48,25 +43,7 @@ import java.util.zip.Checksum;
*
* @author xiaoleilu
*/
public class IoUtil {
/**
* 默认缓存大小 8192
*/
public static final int DEFAULT_BUFFER_SIZE = 2 << 12;
/**
* 默认中等缓存大小 16384
*/
public static final int DEFAULT_MIDDLE_BUFFER_SIZE = 2 << 13;
/**
* 默认大缓存大小 32768
*/
public static final int DEFAULT_LARGE_BUFFER_SIZE = 2 << 14;
/**
* 数据流末尾
*/
public static final int EOF = -1;
public class IoUtil extends NioUtil{
// -------------------------------------------------------------------------------------- Copy start
@ -195,21 +172,6 @@ public class IoUtil {
return size;
}
/**
* 拷贝流 thanks to: https://github.com/venusdrogon/feilong-io/blob/master/src/main/java/com/feilong/io/IOWriteUtil.java<br>
* 本方法不会关闭流
*
* @param in 输入流
* @param out 输出流
* @param bufferSize 缓存大小
* @param streamProgress 进度条
* @return 传输的byte数
* @throws IORuntimeException IO异常
*/
public static long copyByNIO(InputStream in, OutputStream out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
return copy(Channels.newChannel(in), Channels.newChannel(out), bufferSize, streamProgress);
}
/**
* 拷贝文件流使用NIO
*
@ -227,79 +189,13 @@ public class IoUtil {
try {
inChannel = in.getChannel();
outChannel = out.getChannel();
return inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (IOException e) {
throw new IORuntimeException(e);
return copy(inChannel, outChannel);
} finally {
close(outChannel);
close(inChannel);
}
}
/**
* 拷贝流使用NIO不会关闭流
*
* @param in {@link ReadableByteChannel}
* @param out {@link WritableByteChannel}
* @return 拷贝的字节数
* @throws IORuntimeException IO异常
* @since 4.5.0
*/
public static long copy(ReadableByteChannel in, WritableByteChannel out) throws IORuntimeException {
return copy(in, out, DEFAULT_BUFFER_SIZE);
}
/**
* 拷贝流使用NIO不会关闭流
*
* @param in {@link ReadableByteChannel}
* @param out {@link WritableByteChannel}
* @param bufferSize 缓冲大小如果小于等于0使用默认
* @return 拷贝的字节数
* @throws IORuntimeException IO异常
* @since 4.5.0
*/
public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize) throws IORuntimeException {
return copy(in, out, bufferSize, null);
}
/**
* 拷贝流使用NIO不会关闭流
*
* @param in {@link ReadableByteChannel}
* @param out {@link WritableByteChannel}
* @param bufferSize 缓冲大小如果小于等于0使用默认
* @param streamProgress {@link StreamProgress}进度处理器
* @return 拷贝的字节数
* @throws IORuntimeException IO异常
*/
public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
Assert.notNull(in, "InputStream is null !");
Assert.notNull(out, "OutputStream is null !");
ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize <= 0 ? DEFAULT_BUFFER_SIZE : bufferSize);
long size = 0;
if (null != streamProgress) {
streamProgress.start();
}
try {
while (in.read(byteBuffer) != EOF) {
byteBuffer.flip();// 写转读
size += out.write(byteBuffer);
byteBuffer.clear();
if (null != streamProgress) {
streamProgress.progress(size);
}
}
} catch (IOException e) {
throw new IORuntimeException(e);
}
if (null != streamProgress) {
streamProgress.finish();
}
return size;
}
// -------------------------------------------------------------------------------------- Copy end
// -------------------------------------------------------------------------------------- getReader and getWriter start
@ -455,22 +351,7 @@ public class IoUtil {
* @throws IORuntimeException IO异常
*/
public static String read(InputStream in, Charset charset) throws IORuntimeException {
FastByteArrayOutputStream out = read(in);
return null == charset ? out.toString() : out.toString(charset);
}
/**
* 从流中读取内容读取完毕后并不关闭流
*
* @param channel 可读通道读取完毕后并不关闭通道
* @param charset 字符集
* @return 内容
* @throws IORuntimeException IO异常
* @since 4.5.0
*/
public static String read(ReadableByteChannel channel, Charset charset) throws IORuntimeException {
FastByteArrayOutputStream out = read(channel);
return null == charset ? out.toString() : out.toString(charset);
return StrUtil.str(readBytes(in), charset);
}
/**
@ -486,19 +367,6 @@ public class IoUtil {
return out;
}
/**
* 从流中读取内容读到输出流中
*
* @param channel 可读通道读取完毕后并不关闭通道
* @return 输出流
* @throws IORuntimeException IO异常
*/
public static FastByteArrayOutputStream read(ReadableByteChannel channel) throws IORuntimeException {
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
copy(channel, Channels.newChannel(out));
return out;
}
/**
* 从Reader中读取String读取完毕后关闭Reader
*
@ -527,55 +395,14 @@ public class IoUtil {
}
} catch (IOException e) {
throw new IORuntimeException(e);
} finally{
if(isClose){
} finally {
if (isClose) {
IoUtil.close(reader);
}
}
return builder.toString();
}
/**
* 从FileChannel中读取UTF-8编码内容
*
* @param fileChannel 文件管道
* @return 内容
* @throws IORuntimeException IO异常
*/
public static String readUtf8(FileChannel fileChannel) throws IORuntimeException {
return read(fileChannel, CharsetUtil.CHARSET_UTF_8);
}
/**
* 从FileChannel中读取内容读取完毕后并不关闭Channel
*
* @param fileChannel 文件管道
* @param charsetName 字符集
* @return 内容
* @throws IORuntimeException IO异常
*/
public static String read(FileChannel fileChannel, String charsetName) throws IORuntimeException {
return read(fileChannel, CharsetUtil.charset(charsetName));
}
/**
* 从FileChannel中读取内容
*
* @param fileChannel 文件管道
* @param charset 字符集
* @return 内容
* @throws IORuntimeException IO异常
*/
public static String read(FileChannel fileChannel, Charset charset) throws IORuntimeException {
MappedByteBuffer buffer;
try {
buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()).load();
} catch (IOException e) {
throw new IORuntimeException(e);
}
return StrUtil.str(buffer, charset);
}
/**
* 从流中读取bytes读取完毕后关闭流
*
@ -597,12 +424,19 @@ public class IoUtil {
* @since 5.0.4
*/
public static byte[] readBytes(InputStream in, boolean isCloseStream) throws IORuntimeException {
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
copy(in, out);
if (isCloseStream) {
close(in);
final InputStream availableStream = toAvailableStream(in);
try{
final int available = availableStream.available();
if(available > 0){
byte[] result = new byte[available];
//noinspection ResultOfMethodCallIgnored
availableStream.read(result);
return result;
}
return out.toByteArray();
} catch (IOException e){
throw new IORuntimeException(e);
}
return new byte[0];
}
/**
@ -966,6 +800,42 @@ public class IoUtil {
return (in instanceof PushbackInputStream) ? (PushbackInputStream) in : new PushbackInputStream(in, pushBackSize);
}
/**
* 将指定{@link InputStream} 转换为{@link InputStream#available()}方法可用的流<br>
* 在Socket通信流中服务端未返回数据情况下{@link InputStream#available()}方法始终为{@code 0}<br>
* 因此在读取前需要调用{@link InputStream#read()}读取一个字节未返回会阻塞一旦读取到了{@link InputStream#available()}方法就正常了<br>
* 此方法返回对象的规则为
*
* <ul>
* <li>FileInputStream 返回原对象因为文件流的available方法本身可用</li>
* <li>其它InputStream 返回PushbackInputStream</li>
* </ul>
*
* @param in 被转换的流
* @return 转换后的流可能为{@link PushbackInputStream}
* @since 5.5.3
*/
public static InputStream toAvailableStream(InputStream in) {
if(in instanceof FileInputStream){
// FileInputStream本身支持available方法
return in;
}
final PushbackInputStream pushbackInputStream = toPushbackStream(in, 1);
try {
final int available = pushbackInputStream.available();
if (available <= 0) {
//此操作会阻塞直到有数据被读到
int b = pushbackInputStream.read();
pushbackInputStream.unread(b);
}
} catch (IOException e) {
throw new IORuntimeException(e);
}
return pushbackInputStream;
}
/**
* 将byte[]写到流中
*
@ -1113,22 +983,6 @@ public class IoUtil {
}
}
/**
* 关闭<br>
* 关闭失败不会抛出异常
*
* @param closeable 被关闭的对象
*/
public static void close(AutoCloseable closeable) {
if (null != closeable) {
try {
closeable.close();
} catch (Exception e) {
// 静默关闭
}
}
}
/**
* 尝试关闭指定对象<br>
* 判断对象如果实现了{@link AutoCloseable}则调用之

View File

@ -0,0 +1,227 @@
package cn.hutool.core.io;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
/**
* NIO相关工具封装主要针对Channel读写拷贝等封装
*
* @author looly
* @since 5.5.3
*/
public class NioUtil {
/**
* 默认缓存大小 8192
*/
public static final int DEFAULT_BUFFER_SIZE = 2 << 12;
/**
* 默认中等缓存大小 16384
*/
public static final int DEFAULT_MIDDLE_BUFFER_SIZE = 2 << 13;
/**
* 默认大缓存大小 32768
*/
public static final int DEFAULT_LARGE_BUFFER_SIZE = 2 << 14;
/**
* 数据流末尾
*/
public static final int EOF = -1;
/**
* 拷贝流 thanks to: https://github.com/venusdrogon/feilong-io/blob/master/src/main/java/com/feilong/io/IOWriteUtil.java<br>
* 本方法不会关闭流
*
* @param in 输入流
* @param out 输出流
* @param bufferSize 缓存大小
* @param streamProgress 进度条
* @return 传输的byte数
* @throws IORuntimeException IO异常
*/
public static long copyByNIO(InputStream in, OutputStream out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
return copy(Channels.newChannel(in), Channels.newChannel(out), bufferSize, streamProgress);
}
/**
* 拷贝文件Channel使用NIO拷贝后不会关闭channel
*
* @param inChannel {@link FileChannel}
* @param outChannel {@link FileChannel}
* @return 拷贝的字节数
* @throws IORuntimeException IO异常
* @since 5.5.3
*/
public static long copy(FileChannel inChannel, FileChannel outChannel) throws IORuntimeException {
Assert.notNull(inChannel, "In channel is null!");
Assert.notNull(outChannel, "Out channel is null!");
try {
return inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/**
* 拷贝流使用NIO不会关闭channel
*
* @param in {@link ReadableByteChannel}
* @param out {@link WritableByteChannel}
* @return 拷贝的字节数
* @throws IORuntimeException IO异常
* @since 4.5.0
*/
public static long copy(ReadableByteChannel in, WritableByteChannel out) throws IORuntimeException {
return copy(in, out, DEFAULT_BUFFER_SIZE);
}
/**
* 拷贝流使用NIO不会关闭channel
*
* @param in {@link ReadableByteChannel}
* @param out {@link WritableByteChannel}
* @param bufferSize 缓冲大小如果小于等于0使用默认
* @return 拷贝的字节数
* @throws IORuntimeException IO异常
* @since 4.5.0
*/
public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize) throws IORuntimeException {
return copy(in, out, bufferSize, null);
}
/**
* 拷贝流使用NIO不会关闭channel
*
* @param in {@link ReadableByteChannel}
* @param out {@link WritableByteChannel}
* @param bufferSize 缓冲大小如果小于等于0使用默认
* @param streamProgress {@link StreamProgress}进度处理器
* @return 拷贝的字节数
* @throws IORuntimeException IO异常
*/
public static long copy(ReadableByteChannel in, WritableByteChannel out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
Assert.notNull(in, "InputStream is null !");
Assert.notNull(out, "OutputStream is null !");
ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize <= 0 ? DEFAULT_BUFFER_SIZE : bufferSize);
long size = 0;
if (null != streamProgress) {
streamProgress.start();
}
try {
while (in.read(byteBuffer) != EOF) {
byteBuffer.flip();// 写转读
size += out.write(byteBuffer);
byteBuffer.clear();
if (null != streamProgress) {
streamProgress.progress(size);
}
}
} catch (IOException e) {
throw new IORuntimeException(e);
}
if (null != streamProgress) {
streamProgress.finish();
}
return size;
}
/**
* 从流中读取内容读取完毕后并不关闭流
*
* @param channel 可读通道读取完毕后并不关闭通道
* @param charset 字符集
* @return 内容
* @throws IORuntimeException IO异常
* @since 4.5.0
*/
public static String read(ReadableByteChannel channel, Charset charset) throws IORuntimeException {
FastByteArrayOutputStream out = read(channel);
return null == charset ? out.toString() : out.toString(charset);
}
/**
* 从流中读取内容读到输出流中
*
* @param channel 可读通道读取完毕后并不关闭通道
* @return 输出流
* @throws IORuntimeException IO异常
*/
public static FastByteArrayOutputStream read(ReadableByteChannel channel) throws IORuntimeException {
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
copy(channel, Channels.newChannel(out));
return out;
}
/**
* 从FileChannel中读取UTF-8编码内容
*
* @param fileChannel 文件管道
* @return 内容
* @throws IORuntimeException IO异常
*/
public static String readUtf8(FileChannel fileChannel) throws IORuntimeException {
return read(fileChannel, CharsetUtil.CHARSET_UTF_8);
}
/**
* 从FileChannel中读取内容读取完毕后并不关闭Channel
*
* @param fileChannel 文件管道
* @param charsetName 字符集
* @return 内容
* @throws IORuntimeException IO异常
*/
public static String read(FileChannel fileChannel, String charsetName) throws IORuntimeException {
return read(fileChannel, CharsetUtil.charset(charsetName));
}
/**
* 从FileChannel中读取内容
*
* @param fileChannel 文件管道
* @param charset 字符集
* @return 内容
* @throws IORuntimeException IO异常
*/
public static String read(FileChannel fileChannel, Charset charset) throws IORuntimeException {
MappedByteBuffer buffer;
try {
buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()).load();
} catch (IOException e) {
throw new IORuntimeException(e);
}
return StrUtil.str(buffer, charset);
}
/**
* 关闭<br>
* 关闭失败不会抛出异常
*
* @param closeable 被关闭的对象
*/
public static void close(AutoCloseable closeable) {
if (null != closeable) {
try {
closeable.close();
} catch (Exception e) {
// 静默关闭
}
}
}
}

View File

@ -198,7 +198,7 @@ public class PathUtil {
* @since 5.5.1
*/
public static Path copy(Path src, Path target, CopyOption... options) throws IORuntimeException {
if(isFile(src, false)){
if (isFile(src, false)) {
return copyFile(src, target, options);
}
return copyContent(src, target.resolve(src.getFileName()), options);
@ -487,4 +487,17 @@ public class PathUtil {
public static boolean isSymlink(Path path) {
return Files.isSymbolicLink(path);
}
/**
* 判断文件或目录是否存在
*
* @param path 文件
* @param isFollowLinks 是否跟踪软链快捷方式
* @return 是否存在
* @since 5.5.3
*/
public static boolean exists(Path path, boolean isFollowLinks) {
final LinkOption[] options = isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
return Files.exists(path, options);
}
}

View File

@ -0,0 +1,34 @@
package cn.hutool.core.lang;
/**
* 片段默认实现
*
* @param <T> 数字类型用于表示位置index
* @author looly
* @since 5.5.3
*/
public class DefaultSegment<T extends Number> implements Segment<T> {
protected T startIndex;
protected T endIndex;
/**
* 构造
* @param startIndex 起始位置
* @param endIndex 结束位置
*/
public DefaultSegment(T startIndex, T endIndex) {
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public T getStartIndex() {
return this.startIndex;
}
@Override
public T getEndIndex() {
return this.endIndex;
}
}

View File

@ -0,0 +1,41 @@
package cn.hutool.core.lang;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.NumberUtil;
import java.lang.reflect.Type;
/**
* 片段表示用于表示文本集合等数据结构的一个区间
* @param <T> 数字类型用于表示位置index
*
* @author looly
* @since 5.5.3
*/
public interface Segment<T extends Number> {
/**
* 获取起始位置
*
* @return 起始位置
*/
T getStartIndex();
/**
* 获取结束位置
*
* @return 结束位置
*/
T getEndIndex();
/**
* 片段长度默认计算方法为abs({@link #getEndIndex()} - {@link #getEndIndex()})
*
* @return 片段长度
*/
default T length(){
final T start = Assert.notNull(getStartIndex(), "Start index must be not null!");
final T end = Assert.notNull(getEndIndex(), "End index must be not null!");
return Convert.convert((Type) start.getClass(), NumberUtil.sub(end, start).abs());
}
}

View File

@ -1160,6 +1160,21 @@ public class MapUtil {
return null == map ? null : Convert.convert(type, map.get(key), defaultValue);
}
/**
* 获取Map指定key的值并转换为指定类型此方法在转换失败后不抛异常返回null
*
* @param <T> 目标值类型
* @param map Map
* @param key
* @param type 值类型
* @param defaultValue 默认值
* @return
* @since 5.5.3
*/
public static <T> T getQuietly(Map<?, ?> map, Object key, Class<T> type, T defaultValue) {
return null == map ? null : Convert.convertQuietly(type, map.get(key), defaultValue);
}
/**
* 获取Map指定key的值并转换为指定类型
*
@ -1189,6 +1204,21 @@ public class MapUtil {
return null == map ? null : Convert.convert(type, map.get(key), defaultValue);
}
/**
* 获取Map指定key的值并转换为指定类型转换失败后返回null不抛异常
*
* @param <T> 目标值类型
* @param map Map
* @param key
* @param type 值类型
* @param defaultValue 默认值
* @return
* @since 5.5.3
*/
public static <T> T getQuietly(Map<?, ?> map, Object key, TypeReference<T> type, T defaultValue) {
return null == map ? null : Convert.convertQuietly(type, map.get(key), defaultValue);
}
/**
* 重命名键<br>
* 实现方式为一处然后重新put当旧的key不存在直接返回<br>

View File

@ -0,0 +1,41 @@
package cn.hutool.core.net;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
/**
* 账号密码形式的{@link Authenticator} 实现
*
* @author looly
* @since 5.5.3
*/
public class PassAuth extends Authenticator {
/**
* 创建账号密码形式的{@link Authenticator} 实现
*
* @param user 用户名
* @param pass 密码
* @return PassAuth
*/
public static PassAuth of(String user, char[] pass) {
return new PassAuth(user, pass);
}
private final PasswordAuthentication auth;
/**
* 构造
*
* @param user 用户名
* @param pass 密码
*/
public PassAuth(String user, char[] pass) {
auth = new PasswordAuthentication(user, pass);
}
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return auth;
}
}

View File

@ -26,7 +26,7 @@ public class URLEncoder implements Serializable {
// --------------------------------------------------------------------------------------------- Static method start
/**
* 默认{@link URLEncoder}<br>
* 默认URLEncoder<br>
* 默认的编码器针对URI路径编码定义如下
*
* <pre>
@ -38,7 +38,7 @@ public class URLEncoder implements Serializable {
public static final URLEncoder DEFAULT = createDefault();
/**
* 用于查询语句的{@link URLEncoder}<br>
* 用于查询语句的URLEncoder<br>
* 编码器针对URI路径编码定义如下
*
* <pre>
@ -53,7 +53,7 @@ public class URLEncoder implements Serializable {
public static final URLEncoder QUERY = createQuery();
/**
* 全编码的{@link URLEncoder}<br>
* 全编码的URLEncoder<br>
* <pre>
* 0x2A, 0x2D, 0x2E, 0x30 to 0x39, 0x41 to 0x5A, 0x5F, 0x61 to 0x7A as-is
* '*', '-', '.', '0' to '9', 'A' to 'Z', '_', 'a' to 'z' 不编码
@ -63,7 +63,7 @@ public class URLEncoder implements Serializable {
public static final URLEncoder ALL = createAll();
/**
* 创建默认{@link URLEncoder}<br>
* 创建默认URLEncoder<br>
* 默认的编码器针对URI路径编码定义如下
*
* <pre>
@ -72,7 +72,7 @@ public class URLEncoder implements Serializable {
* sub-delims = "!" / "$" / "&amp;" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
* </pre>
*
* @return {@link URLEncoder}
* @return URLEncoder
*/
public static URLEncoder createDefault() {
final URLEncoder encoder = new URLEncoder();
@ -102,7 +102,7 @@ public class URLEncoder implements Serializable {
}
/**
* 创建用于查询语句的{@link URLEncoder}<br>
* 创建用于查询语句的URLEncoder<br>
* 编码器针对URI路径编码定义如下
*
* <pre>
@ -114,7 +114,7 @@ public class URLEncoder implements Serializable {
* <p>
* 详细见https://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm
*
* @return {@link URLEncoder}
* @return URLEncoder
*/
public static URLEncoder createQuery() {
final URLEncoder encoder = new URLEncoder();
@ -133,7 +133,7 @@ public class URLEncoder implements Serializable {
}
/**
* 创建{@link URLEncoder}<br>
* 创建URLEncoder<br>
* 编码器针对URI路径编码定义如下
*
* <pre>
@ -144,7 +144,7 @@ public class URLEncoder implements Serializable {
* <p>
* 详细见https://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm
*
* @return {@link URLEncoder}
* @return URLEncoder
*/
public static URLEncoder createAll() {
final URLEncoder encoder = new URLEncoder();

View File

@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.multi.ListValueMap;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
@ -78,10 +77,7 @@ public class MultipartFormData {
putFile(header.formFieldName, newFile);
} else {
// 标准表单项
ByteArrayOutputStream fbos = new ByteArrayOutputStream(1024);
input.copy(fbos);
String value = (charset != null) ? new String(fbos.toByteArray(), charset) : new String(fbos.toByteArray());
putParameter(header.formFieldName, value);
putParameter(header.formFieldName, input.readString(charset));
}
input.skipBytes(1);

View File

@ -1,5 +1,7 @@
package cn.hutool.core.net.multipart;
import cn.hutool.core.io.FastByteArrayOutputStream;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -144,7 +146,20 @@ public class MultipartRequestInputStream extends BufferedInputStream {
// ---------------------------------------------------------------- copy
/**
* 全部字节流复制到out
* 读取字节流直到下一个boundary
*
* @param charset 编码null表示系统默认编码
* @return 读取的字符串
* @throws IOException 读取异常
*/
public String readString(Charset charset) throws IOException {
final FastByteArrayOutputStream out = new FastByteArrayOutputStream();
copy(out);
return out.toString(charset);
}
/**
* 字节流复制到out直到下一个boundary
*
* @param out 输出流
* @return 复制的字节数
@ -171,7 +186,7 @@ public class MultipartRequestInputStream extends BufferedInputStream {
* @return 复制的字节数
* @throws IOException 读取异常
*/
public int copy(OutputStream out, int limit) throws IOException {
public int copy(OutputStream out, long limit) throws IOException {
int count = 0;
while (true) {
byte b = readByte();

View File

@ -7,7 +7,6 @@ import cn.hutool.core.util.StrUtil;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@ -24,7 +23,7 @@ public class UploadFile {
private final UploadFileHeader header;
private final UploadSetting setting;
private int size = -1;
private long size = -1;
// 文件流小文件位于内存中
private byte[] data;
@ -150,7 +149,7 @@ public class UploadFile {
/**
* @return 上传文件的大小&gt; 0 表示未上传
*/
public int size() {
public long size() {
return size;
}
@ -200,13 +199,13 @@ public class UploadFile {
// 处理硬盘文件
tempFile = FileUtil.createTempFile(TMP_FILE_PREFIX, TMP_FILE_SUFFIX, FileUtil.touch(setting.tmpUploadPath), false);
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile));
final BufferedOutputStream out = FileUtil.getOutputStream(this.tempFile);
if (data != null) {
size = data.length;
out.write(data);
data = null; // not needed anymore
}
int maxFileSize = setting.maxFileSize;
final long maxFileSize = setting.maxFileSize;
try {
if (maxFileSize == -1) {
size += input.copy(out);
@ -236,14 +235,14 @@ public class UploadFile {
* @return 是否为允许的扩展名
*/
private boolean isAllowedExtension() {
String[] exts = setting.fileExts;
final String[] exts = setting.fileExts;
boolean isAllow = setting.isAllowFileExts;
if (exts == null || exts.length == 0) {
// 如果给定扩展名列表为空当允许扩展名时全部允许否则全部禁止
return isAllow;
}
String fileNameExt = FileUtil.extName(this.getFileName());
final String fileNameExt = FileUtil.extName(this.getFileName());
for (String fileExtension : setting.fileExts) {
if (fileNameExt.equalsIgnoreCase(fileExtension)) {
return isAllow;

View File

@ -31,7 +31,8 @@ public class UploadFileHeader {
// ---------------------------------------------------------------- public interface
/**
* Returns <code>true</code> if uploaded data are correctly marked as a file. This is true if header contains string 'filename'.
* Returns {@code true} if uploaded data are correctly marked as a file.<br>
* This is true if header contains string 'filename'.
*
* @return 是否为文件
*/

View File

@ -9,7 +9,7 @@ package cn.hutool.core.net.multipart;
public class UploadSetting {
/** 最大文件大小,默认无限制 */
protected int maxFileSize = -1;
protected long maxFileSize = -1;
/** 文件保存到内存的边界 */
protected int memoryThreshold = 8192;
/** 临时文件目录 */
@ -26,7 +26,7 @@ public class UploadSetting {
/**
* @return 获得最大文件大小-1表示无限制
*/
public int getMaxFileSize() {
public long getMaxFileSize() {
return maxFileSize;
}
@ -35,7 +35,7 @@ public class UploadSetting {
*
* @param maxFileSize 最大文件大小
*/
public void setMaxFileSize(int maxFileSize) {
public void setMaxFileSize(long maxFileSize) {
this.maxFileSize = maxFileSize;
}

File diff suppressed because it is too large Load Diff

View File

@ -239,7 +239,7 @@ public class StrBuilder implements CharSequence, Appendable, Serializable {
*/
public StrBuilder insert(int index, CharSequence csq) {
if (null == csq) {
csq = "null";
csq = StrUtil.EMPTY;
}
int len = csq.length();
moveDataAfterIndex(index, csq.length());
@ -523,7 +523,8 @@ public class StrBuilder implements CharSequence, Appendable, Serializable {
* @param minimumCapacity 最小容量
*/
private void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > value.length) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
expandCapacity(minimumCapacity);
}
}
@ -535,8 +536,9 @@ public class StrBuilder implements CharSequence, Appendable, Serializable {
* @param minimumCapacity 需要扩展的最小容量
*/
private void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity < minimumCapacity) {
int newCapacity = (value.length << 1) + 2;
// overflow-conscious code
if (newCapacity - minimumCapacity < 0) {
newCapacity = minimumCapacity;
}
if (newCapacity < 0) {

View File

@ -83,6 +83,8 @@ public class IdcardUtil {
CITY_CODES.put("71", "台湾");
CITY_CODES.put("81", "香港");
CITY_CODES.put("82", "澳门");
//issue#1277台湾身份证号码以83开头但是行政区划为71
CITY_CODES.put("83", "台湾");
CITY_CODES.put("91", "国外");
TW_FIRST_CODE.put("A", 10);
@ -354,7 +356,7 @@ public class IdcardUtil {
sum = sum + Integer.parseInt(String.valueOf(c)) * iflag;
iflag--;
}
if ("A".equals(end.toUpperCase())) {
if ("A".equalsIgnoreCase(end)) {
sum += 10;
} else {
sum += Integer.parseInt(end);
@ -508,7 +510,7 @@ public class IdcardUtil {
}
/**
* 根据身份编号获取户籍省份只支持15或18位身份证号码
* 根据身份编号获取市级编码只支持15或18位身份证号码
*
* @param idcard 身份编码
* @return 市级编码
@ -656,9 +658,9 @@ public class IdcardUtil {
}
/**
* 获取省份代
* 获取市级编
*
* @return 省份代
* @return 市级编
*/
public String getCityCode() {
return this.cityCode;

View File

@ -41,7 +41,7 @@ public class NumberUtil {
/**
* 默认除法运算精度
*/
private static final int DEFAUT_DIV_SCALE = 10;
private static final int DEFAULT_DIV_SCALE = 10;
/**
* 0-20对应的阶乘超过20的阶乘会超过Long.MAX_VALUE
@ -484,7 +484,7 @@ public class NumberUtil {
* @return 两个参数的商
*/
public static double div(float v1, float v2) {
return div(v1, v2, DEFAUT_DIV_SCALE);
return div(v1, v2, DEFAULT_DIV_SCALE);
}
/**
@ -495,7 +495,7 @@ public class NumberUtil {
* @return 两个参数的商
*/
public static double div(float v1, double v2) {
return div(v1, v2, DEFAUT_DIV_SCALE);
return div(v1, v2, DEFAULT_DIV_SCALE);
}
/**
@ -506,7 +506,7 @@ public class NumberUtil {
* @return 两个参数的商
*/
public static double div(double v1, float v2) {
return div(v1, v2, DEFAUT_DIV_SCALE);
return div(v1, v2, DEFAULT_DIV_SCALE);
}
/**
@ -517,7 +517,7 @@ public class NumberUtil {
* @return 两个参数的商
*/
public static double div(double v1, double v2) {
return div(v1, v2, DEFAUT_DIV_SCALE);
return div(v1, v2, DEFAULT_DIV_SCALE);
}
/**
@ -528,7 +528,7 @@ public class NumberUtil {
* @return 两个参数的商
*/
public static double div(Double v1, Double v2) {
return div(v1, v2, DEFAUT_DIV_SCALE);
return div(v1, v2, DEFAULT_DIV_SCALE);
}
/**
@ -540,7 +540,7 @@ public class NumberUtil {
* @since 3.1.0
*/
public static BigDecimal div(Number v1, Number v2) {
return div(v1, v2, DEFAUT_DIV_SCALE);
return div(v1, v2, DEFAULT_DIV_SCALE);
}
/**
@ -551,7 +551,7 @@ public class NumberUtil {
* @return 两个参数的商
*/
public static BigDecimal div(String v1, String v2) {
return div(v1, v2, DEFAUT_DIV_SCALE);
return div(v1, v2, DEFAULT_DIV_SCALE);
}
/**
@ -1516,7 +1516,7 @@ public class NumberUtil {
*/
public static int processMultiple(int selectNum, int minNum) {
int result;
result = mathSubnode(selectNum, minNum) / mathNode(selectNum - minNum);
result = mathSubNode(selectNum, minNum) / mathNode(selectNum - minNum);
return result;
}
@ -2494,11 +2494,11 @@ public class NumberUtil {
}
// ------------------------------------------------------------------------------------------- Private method start
private static int mathSubnode(int selectNum, int minNum) {
private static int mathSubNode(int selectNum, int minNum) {
if (selectNum == minNum) {
return 1;
} else {
return selectNum * mathSubnode(selectNum - 1, minNum);
return selectNum * mathSubNode(selectNum - 1, minNum);
}
}

View File

@ -383,7 +383,7 @@ public class ObjectUtil {
* 克隆对象<br>
* 如果对象实现Cloneable接口调用其clone方法<br>
* 如果实现Serializable接口执行深度克隆<br>
* 否则返回<code>null</code>
* 否则返回{@code null}
*
* @param <T> 对象类型
* @param obj 被克隆对象
@ -606,11 +606,24 @@ public class ObjectUtil {
return ArrayUtil.emptyCount(objs);
}
/**
* 是否存在{@code null}对象通过{@link ObjectUtil#isNull(Object)} 判断元素
*
* @param objs 被检查对象
* @return 是否存在
* @since 5.5.3
* @see ArrayUtil#hasNull(Object[])
*/
public static boolean hasNull(Object... objs) {
return ArrayUtil.hasNull(objs);
}
/**
* 是否存在{@code null}或空对象通过{@link ObjectUtil#isEmpty(Object)} 判断元素
*
* @param objs 被检查对象
* @return 是否存在
* @see ArrayUtil#hasEmpty(Object...)
*/
public static boolean hasEmpty(Object... objs) {
return ArrayUtil.hasEmpty(objs);

View File

@ -1,5 +1,8 @@
package cn.hutool.core.util;
import cn.hutool.core.lang.DefaultSegment;
import cn.hutool.core.lang.Segment;
/**
* 分页工具类
*
@ -135,6 +138,35 @@ public class PageUtil {
return new int[]{start, getEndByStart(start, pageSize)};
}
/**
* 将页数和每页条目数转换为开始位置和结束位置<br>
* 此方法用于包括结束位置的分页方法<br>
* 例如
*
* <pre>
* 页码0每页10 = [0, 10]
* 页码1每页10 = [10, 20]
*
* </pre>
*
* <p>
* {@link #setFirstPageNo(int)}设置为1时
* <pre>
* 页码1每页10 = [0, 10]
* 页码2每页10 = [10, 20]
*
* </pre>
*
* @param pageNo 页码从0计数
* @param pageSize 每页条目数
* @return {@link Segment}
* @since 5.5.3
*/
public static Segment<Integer> toSegment(int pageNo, int pageSize) {
final int[] startEnd = transToStartEnd(pageNo, pageSize);
return new DefaultSegment<>(startEnd[0], startEnd[1]);
}
/**
* 根据总数计算总页数
*

View File

@ -140,7 +140,7 @@ public class ReflectUtil {
}
/**
* 查找指定类中的指定name的字段包括非public字段也包括父类和Object类的字段 字段不存在则返回<code>null</code>
* 查找指定类中的指定name的字段包括非public字段也包括父类和Object类的字段 字段不存在则返回{@code null}
*
* @param beanClass 被查找字段的类,不能为null
* @param name 字段名
@ -422,7 +422,7 @@ public class ReflectUtil {
}
/**
* 查找指定Public方法 如果找不到对应的方法或方法不为public的则返回<code>null</code>
* 查找指定Public方法 如果找不到对应的方法或方法不为public的则返回{@code null}
*
* @param clazz
* @param methodName 方法名
@ -442,7 +442,7 @@ public class ReflectUtil {
* 查找指定对象中的所有方法包括非public方法也包括父对象和Object类的方法
*
* <p>
* 此方法为精准获取方法名即方法名和参数数量和类型必须一致否则返回<code>null</code>
* 此方法为精准获取方法名即方法名和参数数量和类型必须一致否则返回{@code null}
* </p>
*
* @param obj 被查找的对象如果为{@code null}返回{@code null}
@ -459,10 +459,10 @@ public class ReflectUtil {
}
/**
* 忽略大小写查找指定方法如果找不到对应的方法则返回<code>null</code>
* 忽略大小写查找指定方法如果找不到对应的方法则返回{@code null}
*
* <p>
* 此方法为精准获取方法名即方法名和参数数量和类型必须一致否则返回<code>null</code>
* 此方法为精准获取方法名即方法名和参数数量和类型必须一致否则返回{@code null}
* </p>
*
* @param clazz 如果为{@code null}返回{@code null}
@ -477,10 +477,10 @@ public class ReflectUtil {
}
/**
* 查找指定方法 如果找不到对应的方法则返回<code>null</code>
* 查找指定方法 如果找不到对应的方法则返回{@code null}
*
* <p>
* 此方法为精准获取方法名即方法名和参数数量和类型必须一致否则返回<code>null</code>
* 此方法为精准获取方法名即方法名和参数数量和类型必须一致否则返回{@code null}
* </p>
*
* @param clazz 如果为{@code null}返回{@code null}
@ -494,10 +494,10 @@ public class ReflectUtil {
}
/**
* 查找指定方法 如果找不到对应的方法则返回<code>null</code>
* 查找指定方法 如果找不到对应的方法则返回{@code null}
*
* <p>
* 此方法为精准获取方法名即方法名和参数数量和类型必须一致否则返回<code>null</code>
* 此方法为精准获取方法名即方法名和参数数量和类型必须一致否则返回{@code null}
* </p>
*
* @param clazz 如果为{@code null}返回{@code null}
@ -527,7 +527,7 @@ public class ReflectUtil {
}
/**
* 按照方法名查找指定方法名的方法只返回匹配到的第一个方法如果找不到对应的方法则返回<code>null</code>
* 按照方法名查找指定方法名的方法只返回匹配到的第一个方法如果找不到对应的方法则返回{@code null}
*
* <p>
* 此方法只检查方法名是否一致并不检查参数的一致性
@ -544,7 +544,7 @@ public class ReflectUtil {
}
/**
* 按照方法名查找指定方法名的方法只返回匹配到的第一个方法如果找不到对应的方法则返回<code>null</code>
* 按照方法名查找指定方法名的方法只返回匹配到的第一个方法如果找不到对应的方法则返回{@code null}
*
* <p>
* 此方法只检查方法名是否一致忽略大小写并不检查参数的一致性
@ -561,7 +561,7 @@ public class ReflectUtil {
}
/**
* 按照方法名查找指定方法名的方法只返回匹配到的第一个方法如果找不到对应的方法则返回<code>null</code>
* 按照方法名查找指定方法名的方法只返回匹配到的第一个方法如果找不到对应的方法则返回{@code null}
*
* <p>
* 此方法只检查方法名是否一致并不检查参数的一致性
@ -842,7 +842,7 @@ public class ReflectUtil {
* </pre>
*
* @param <T> 返回对象类型
* @param obj 对象如果执行静态方法此值为<code>null</code>
* @param obj 对象如果执行静态方法此值为{@code null}
* @param method 方法对象方法或static方法都可
* @param args 参数对象
* @return 结果
@ -878,7 +878,7 @@ public class ReflectUtil {
* </pre>
*
* @param <T> 返回对象类型
* @param obj 对象如果执行静态方法此值为<code>null</code>
* @param obj 对象如果执行静态方法此值为{@code null}
* @param method 方法对象方法或static方法都可
* @param args 参数对象
* @return 结果

File diff suppressed because it is too large Load Diff

View File

@ -65,6 +65,36 @@ import java.util.Map;
*/
public class XmlUtil {
/**
* 字符串常量XML 空格转义 {@code "&nbsp;" -> " "}
*/
public static final String NBSP = "&nbsp;";
/**
* 字符串常量XML And 符转义 {@code "&amp;" -> "&"}
*/
public static final String AMP = "&amp;";
/**
* 字符串常量XML 双引号转义 {@code "&quot;" -> "\""}
*/
public static final String QUOTE = "&quot;";
/**
* 字符串常量XML 单引号转义 {@code "&apos" -> "'"}
*/
public static final String APOS = "&apos;";
/**
* 字符串常量XML 小于号转义 {@code "&lt;" -> "<"}
*/
public static final String LT = "&lt;";
/**
* 字符串常量XML 大于号转义 {@code "&gt;" -> ">"}
*/
public static final String GT = "&gt;";
/**
* 在XML中无效的字符 正则
*/

View File

@ -0,0 +1,23 @@
package cn.hutool.core.date;
import org.junit.Assert;
import org.junit.Test;
import java.time.LocalDate;
import java.time.LocalTime;
public class TemporalAccessorUtilTest {
@Test
public void formatLocalDateTest(){
final String format = TemporalAccessorUtil.format(LocalDate.of(2020, 12, 7), DatePattern.NORM_DATETIME_PATTERN);
Assert.assertEquals("2020-12-07 00:00:00", format);
}
@Test
public void formatLocalTimeTest(){
final String today = TemporalAccessorUtil.format(LocalDate.now(), DatePattern.NORM_DATE_PATTERN);
final String format = TemporalAccessorUtil.format(LocalTime.MIN, DatePattern.NORM_DATETIME_PATTERN);
Assert.assertEquals(today + " 00:00:00", format);
}
}

View File

@ -187,4 +187,12 @@ public class ValidatorTest {
zipCode = Validator.isZipCode("102629");
Assert.assertTrue(zipCode);
}
@Test
public void isBetweenTest() {
Assert.assertTrue(Validator.isBetween(0, 0, 1));
Assert.assertTrue(Validator.isBetween(1L, 0L, 1L));
Assert.assertTrue(Validator.isBetween(0.19f, 0.1f, 0.2f));
Assert.assertTrue(Validator.isBetween(0.19, 0.1, 0.2));
}
}

View File

@ -75,6 +75,12 @@ public class IdcardUtilTest {
Assert.assertEquals(province2, "内蒙古");
}
@Test
public void getCityCodeByIdCardTest() {
String codeByIdCard = IdcardUtil.getCityCodeByIdCard(ID_18);
Assert.assertEquals("32108", codeByIdCard);
}
@Test
public void getGenderByIdCardTest() {
int gender = IdcardUtil.getGenderByIdCard(ID_18);

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</parent>
<artifactId>hutool-cron</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</parent>
<artifactId>hutool-crypto</artifactId>

View File

@ -626,7 +626,7 @@ public final class SecureUtil {
* 创建HMac对象调用digest方法可获得hmac值
*
* @param algorithm {@link HmacAlgorithm}
* @param key 密钥如果为<code>null</code>生成随机密钥
* @param key 密钥如果为{@code null}生成随机密钥
* @return {@link HMac}
* @since 3.3.0
*/
@ -638,7 +638,7 @@ public final class SecureUtil {
* 创建HMac对象调用digest方法可获得hmac值
*
* @param algorithm {@link HmacAlgorithm}
* @param key 密钥如果为<code>null</code>生成随机密钥
* @param key 密钥如果为{@code null}生成随机密钥
* @return {@link HMac}
* @since 3.0.3
*/
@ -650,7 +650,7 @@ public final class SecureUtil {
* 创建HMac对象调用digest方法可获得hmac值
*
* @param algorithm {@link HmacAlgorithm}
* @param key 密钥{@link SecretKey}如果为<code>null</code>生成随机密钥
* @param key 密钥{@link SecretKey}如果为{@code null}生成随机密钥
* @return {@link HMac}
* @since 3.0.3
*/
@ -664,7 +664,7 @@ public final class SecureUtil {
* HmacMD5加密hmacMd5(key).digest(data)<br>
* HmacMD5加密并转为16进制字符串hmacMd5(key).digestHex(data)<br>
*
* @param key 加密密钥如果为<code>null</code>生成随机密钥
* @param key 加密密钥如果为{@code null}生成随机密钥
* @return {@link HMac}
* @since 3.3.0
*/
@ -678,7 +678,7 @@ public final class SecureUtil {
* HmacMD5加密hmacMd5(key).digest(data)<br>
* HmacMD5加密并转为16进制字符串hmacMd5(key).digestHex(data)<br>
*
* @param key 加密密钥如果为<code>null</code>生成随机密钥
* @param key 加密密钥如果为{@code null}生成随机密钥
* @return {@link HMac}
*/
public static HMac hmacMd5(byte[] key) {
@ -703,7 +703,7 @@ public final class SecureUtil {
* HmacSHA1加密hmacSha1(key).digest(data)<br>
* HmacSHA1加密并转为16进制字符串hmacSha1(key).digestHex(data)<br>
*
* @param key 加密密钥如果为<code>null</code>生成随机密钥
* @param key 加密密钥如果为{@code null}生成随机密钥
* @return {@link HMac}
* @since 3.3.0
*/
@ -717,7 +717,7 @@ public final class SecureUtil {
* HmacSHA1加密hmacSha1(key).digest(data)<br>
* HmacSHA1加密并转为16进制字符串hmacSha1(key).digestHex(data)<br>
*
* @param key 加密密钥如果为<code>null</code>生成随机密钥
* @param key 加密密钥如果为{@code null}生成随机密钥
* @return {@link HMac}
*/
public static HMac hmacSha1(byte[] key) {

View File

@ -1,14 +1,5 @@
package cn.hutool.crypto.digest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
@ -19,6 +10,15 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.SecureUtil;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
/**
* 摘要算法<br>
* 注意此对象实例化后为非线程安全
@ -84,7 +84,7 @@ public class Digester implements Serializable {
*
* @param algorithm 算法
* @param provider 算法提供者null表示JDK默认可以引入Bouncy Castle等来提供更多算法支持
* @return {@link Digester}
* @return Digester
* @throws CryptoException Cause by IOException
*/
public Digester init(String algorithm, Provider provider) {

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</parent>
<artifactId>hutool-db</artifactId>

View File

@ -12,6 +12,7 @@ import cn.hutool.db.sql.Condition;
import cn.hutool.db.sql.Condition.LikeType;
import cn.hutool.db.sql.LogicalOperator;
import cn.hutool.db.sql.Query;
import cn.hutool.db.sql.SqlBuilder;
import cn.hutool.db.sql.SqlExecutor;
import cn.hutool.db.sql.SqlUtil;
import cn.hutool.db.sql.Wrapper;
@ -660,7 +661,7 @@ public abstract class AbstractDb implements Serializable {
* @return 复合条件的结果数
* @throws SQLException SQL执行异常
*/
public int count(Entity where) throws SQLException {
public long count(Entity where) throws SQLException {
Connection conn = null;
try {
conn = this.getConnection();
@ -670,6 +671,23 @@ public abstract class AbstractDb implements Serializable {
}
}
/**
* 结果的条目数
*
* @param selectSql 查询SQL语句
* @return 复合条件的结果数
* @throws SQLException SQL执行异常
*/
public long count(CharSequence selectSql) throws SQLException {
Connection conn = null;
try {
conn = this.getConnection();
return runner.count(conn, selectSql);
} finally {
this.closeConnection(conn);
}
}
/**
* 分页查询<br>
* 查询条件为多个key value对表示默认key = value如果使用其它条件可以使用where.put("key", " &gt; 1")value也可以传Condition对象key被忽略
@ -777,25 +795,59 @@ public abstract class AbstractDb implements Serializable {
}
}
/**
* 分页查询<br>
*
* @param <T> 结果对象类型
* @param sql SQL构建器可以使用{@link SqlBuilder#of(CharSequence)} 包装普通SQL
* @param page 分页对象
* @param rsh 结果集处理对象
* @return 结果对象
* @throws SQLException SQL执行异常
* @since 5.5.3
*/
public <T> T page(CharSequence sql, Page page, RsHandler<T> rsh) throws SQLException {
Connection conn = null;
try {
conn = this.getConnection();
return runner.page(conn, SqlBuilder.of(sql), page, rsh);
} finally {
this.closeConnection(conn);
}
}
/**
* 分页查询
*
* @param sql SQL语句字符串
* @param page 分页对象
* @return 结果对象
* @throws SQLException SQL执行异常
* @since 5.5.3
*/
public PageResult<Entity> page(CharSequence sql, Page page) throws SQLException {
Connection conn = null;
try {
conn = this.getConnection();
return runner.page(conn, SqlBuilder.of(sql), page);
} finally {
this.closeConnection(conn);
}
}
/**
* 分页查询<br>
* 查询条件为多个key value对表示默认key = value如果使用其它条件可以使用where.put("key", " &gt; 1")value也可以传Condition对象key被忽略
*
* @param fields 返回的字段列表null则返回所有字段
* @param where 条件实体类包含表名
* @param page 分页对象
* @param numPerPage 每页条目数
* @param pageNumber 页码
* @param pageSize 每页结果
* @return 结果对象
* @throws SQLException SQL执行异常
*/
public PageResult<Entity> page(Collection<String> fields, Entity where, int page, int numPerPage) throws SQLException {
Connection conn = null;
try {
conn = this.getConnection();
return runner.page(conn, fields, where, page, numPerPage);
} finally {
this.closeConnection(conn);
}
public PageResult<Entity> page(Collection<String> fields, Entity where, int pageNumber, int pageSize) throws SQLException {
return page(fields, where, new Page(pageNumber, pageSize));
}
/**

View File

@ -326,7 +326,7 @@ public class DaoTemplate {
* @return 数量
* @throws SQLException SQL执行异常
*/
public int count(Entity where) throws SQLException{
public long count(Entity where) throws SQLException{
return db.count(fixEntity(where));
}

View File

@ -0,0 +1,299 @@
package cn.hutool.db;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.dialect.Dialect;
import cn.hutool.db.dialect.DialectFactory;
import cn.hutool.db.handler.NumberHandler;
import cn.hutool.db.handler.RsHandler;
import cn.hutool.db.sql.Query;
import cn.hutool.db.sql.SqlBuilder;
import cn.hutool.db.sql.SqlExecutor;
import cn.hutool.db.sql.SqlUtil;
import cn.hutool.db.sql.Wrapper;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class DialectRunner implements Serializable {
private static final long serialVersionUID = 1L;
private Dialect dialect;
/**
* 是否大小写不敏感默认大小写不敏感
*/
protected boolean caseInsensitive = GlobalDbConfig.caseInsensitive;
/**
* 构造
*
* @param dialect 方言
*/
public DialectRunner(Dialect dialect) {
this.dialect = dialect;
}
/**
* 构造
*
* @param driverClassName 驱动类名用于识别方言
*/
public DialectRunner(String driverClassName) {
this(DialectFactory.newDialect(driverClassName));
}
//---------------------------------------------------------------------------- CRUD start
/**
* 批量插入数据<br>
* 批量插入必须严格保持Entity的结构一致不一致会导致插入数据出现不可预知的结果<br>
* 此方法不会关闭Connection
*
* @param conn 数据库连接
* @param records 记录列表记录KV必须严格一致
* @return 插入行数
* @throws SQLException SQL执行异常
*/
public int[] insert(Connection conn, Entity... records) throws SQLException {
checkConn(conn);
if (ArrayUtil.isEmpty(records)) {
return new int[]{0};
}
PreparedStatement ps = null;
try {
if(1 == records.length){
//单条单独处理
ps = dialect.psForInsert(conn, records[0]);
return new int[]{ps.executeUpdate()};
}
// 批量
ps = dialect.psForInsertBatch(conn, records);
return ps.executeBatch();
} finally {
DbUtil.close(ps);
}
}
/**
* 插入数据<br>
* 此方法不会关闭Connection
*
* @param conn 数据库连接
* @param record 记录
* @param generatedKeysHandler 自增主键处理器用于定义返回自增主键的范围和类型
* @return 主键列表
* @throws SQLException SQL执行异常
*/
public <T> T insert(Connection conn, Entity record, RsHandler<T> generatedKeysHandler) throws SQLException {
checkConn(conn);
if (CollUtil.isEmpty(record)) {
throw new SQLException("Empty entity provided!");
}
PreparedStatement ps = null;
try {
ps = dialect.psForInsert(conn, record);
ps.executeUpdate();
if(null == generatedKeysHandler){
return null;
}
return StatementUtil.getGeneratedKeys(ps, generatedKeysHandler);
} finally {
DbUtil.close(ps);
}
}
/**
* 删除数据<br>
* 此方法不会关闭Connection
*
* @param conn 数据库连接
* @param where 条件
* @return 影响行数
* @throws SQLException SQL执行异常
*/
public int del(Connection conn, Entity where) throws SQLException {
checkConn(conn);
if (CollUtil.isEmpty(where)) {
//不允许做全表删除
throw new SQLException("Empty entity provided!");
}
PreparedStatement ps = null;
try {
ps = dialect.psForDelete(conn, Query.of(where));
return ps.executeUpdate();
} finally {
DbUtil.close(ps);
}
}
/**
* 更新数据<br>
* 此方法不会关闭Connection
*
* @param conn 数据库连接
* @param record 记录
* @param where 条件
* @return 影响行数
* @throws SQLException SQL执行异常
*/
public int update(Connection conn, Entity record, Entity where) throws SQLException {
checkConn(conn);
if (CollUtil.isEmpty(record)) {
throw new SQLException("Empty entity provided!");
}
if (CollUtil.isEmpty(where)) {
//不允许做全表更新
throw new SQLException("Empty where provided!");
}
//表名可以从被更新记录的Entity中获得也可以从Where中获得
String tableName = record.getTableName();
if (StrUtil.isBlank(tableName)) {
tableName = where.getTableName();
record.setTableName(tableName);
}
final Query query = new Query(SqlUtil.buildConditions(where), tableName);
PreparedStatement ps = null;
try {
ps = dialect.psForUpdate(conn, record, query);
return ps.executeUpdate();
} finally {
DbUtil.close(ps);
}
}
/**
* 查询<br>
* 此方法不会关闭Connection
*
* @param <T> 结果对象类型
* @param conn 数据库连接对象
* @param query {@link Query}
* @param rsh 结果集处理对象
* @return 结果对象
* @throws SQLException SQL执行异常
*/
public <T> T find(Connection conn, Query query, RsHandler<T> rsh) throws SQLException {
checkConn(conn);
Assert.notNull(query, "[query] is null !");
return SqlExecutor.queryAndClosePs(dialect.psForFind(conn, query), rsh);
}
/**
* 获取结果总数生成类似于select count(1) from XXX wher XXX=? and YYY=?
*
* @param conn 数据库连接对象
* @param where 查询条件
* @return 复合条件的结果数
* @throws SQLException SQL执行异常
*/
public long count(Connection conn, Entity where) throws SQLException {
checkConn(conn);
return SqlExecutor.queryAndClosePs(dialect.psForCount(conn, Query.of(where)), new NumberHandler()).longValue();
}
/**
* 分页查询<br>
* 此方法不会关闭Connection
*
* @param <T> 结果对象类型
* @param conn 数据库连接对象
* @param query 查询条件包含表名
* @param rsh 结果集处理对象
* @return 结果对象
* @throws SQLException SQL执行异常
*/
public <T> T page(Connection conn, Query query, RsHandler<T> rsh) throws SQLException {
checkConn(conn);
if (null == query.getPage()) {
return this.find(conn, query, rsh);
}
return SqlExecutor.queryAndClosePs(dialect.psForPage(conn, query), rsh);
}
/**
* 分页查询<br>
* 此方法不会关闭Connection
*
* @param <T> 结果对象类型
* @param conn 数据库连接对象
* @param sqlBuilder SQL构建器可以使用{@link SqlBuilder#of(CharSequence)} 包装普通SQL
* @param page 分页对象
* @param rsh 结果集处理对象
* @return 结果对象
* @throws SQLException SQL执行异常
* @since 5.5.3
*/
public <T> T page(Connection conn, SqlBuilder sqlBuilder, Page page, RsHandler<T> rsh) throws SQLException {
checkConn(conn);
if (null == page) {
return SqlExecutor.query(conn, sqlBuilder, rsh);
}
return SqlExecutor.queryAndClosePs(dialect.psForPage(conn, sqlBuilder, page), rsh);
}
//---------------------------------------------------------------------------- CRUD end
//---------------------------------------------------------------------------- Getters and Setters start
/**
* 设置是否在结果中忽略大小写<br>
* 如果忽略则在Entity中调用getXXX时字段值忽略大小写默认忽略
*
* @param caseInsensitive 否在结果中忽略大小写
* @since 5.2.4
*/
public void setCaseInsensitive(boolean caseInsensitive) {
this.caseInsensitive = caseInsensitive;
}
/**
* @return SQL方言
*/
public Dialect getDialect() {
return dialect;
}
/**
* 设置SQL方言
*
* @param dialect 方言
*/
public void setDialect(Dialect dialect) {
this.dialect = dialect;
}
/**
* 设置包装器包装器用于对表名字段名进行符号包装例如双引号防止关键字与这些表名或字段冲突
*
* @param wrapperChar 包装字符字符会在SQL生成时位于表名和字段名两边null时表示取消包装
*/
public void setWrapper(Character wrapperChar) {
setWrapper(new Wrapper(wrapperChar));
}
/**
* 设置包装器包装器用于对表名字段名进行符号包装例如双引号防止关键字与这些表名或字段冲突
*
* @param wrapper 包装器null表示取消包装
*/
public void setWrapper(Wrapper wrapper) {
this.dialect.setWrapper(wrapper);
}
//---------------------------------------------------------------------------- Getters and Setters end
//---------------------------------------------------------------------------- Private method start
private void checkConn(Connection conn) {
Assert.notNull(conn, "Connection object must be not null!");
}
//---------------------------------------------------------------------------- Private method start
}

View File

@ -1,5 +1,6 @@
package cn.hutool.db;
import cn.hutool.core.lang.Segment;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.PageUtil;
import cn.hutool.db.sql.Order;
@ -11,21 +12,39 @@ import java.util.Arrays;
* 分页对象
*
* @author Looly
*
*/
public class Page implements Serializable {
public class Page implements Segment<Integer>, Serializable {
private static final long serialVersionUID = 97792549823353462L;
public static final int DEFAULT_PAGE_SIZE = 20;
/** 页码0表示第一页 */
/**
* 页码0表示第一页
*/
private int pageNumber;
/** 每页结果数 */
/**
* 每页结果数
*/
private int pageSize;
/** 排序 */
/**
* 排序
*/
private Order[] orders;
/**
* 创建Page对象
*
* @param pageNumber 页码0表示第一页
* @param pageSize 每页结果数
* @return Page
* @since 5.5.3
*/
public static Page of(int pageNumber, int pageSize) {
return new Page(pageNumber, pageSize);
}
// ---------------------------------------------------------- Constructor start
/**
* 构造默认第0页每页{@value #DEFAULT_PAGE_SIZE}
*
@ -55,11 +74,12 @@ public class Page implements Serializable {
*/
public Page(int pageNumber, int pageSize, Order order) {
this(pageNumber, pageSize);
this.orders = new Order[] { order };
this.orders = new Order[]{order};
}
// ---------------------------------------------------------- Constructor start
// ---------------------------------------------------------- Getters and Setters start
/**
* @return 页码0表示第一页
*/
@ -140,15 +160,27 @@ public class Page implements Serializable {
/**
* @return 开始位置
* @see #getStartIndex()
*/
public int getStartPosition() {
return getStartIndex();
}
@Override
public Integer getStartIndex() {
return PageUtil.getStart(this.pageNumber, this.pageSize);
}
/**
* @return 结束位置
* @see #getEndIndex()
*/
public int getEndPosition() {
return getEndIndex();
}
@Override
public Integer getEndIndex() {
return PageUtil.getEnd(this.pageNumber, this.pageSize);
}

View File

@ -31,7 +31,7 @@ public class Session extends AbstractDb implements Closeable {
/**
* 创建默认数据源会话
*
* @return {@link Session}
* @return Session
* @since 3.2.3
*/
public static Session create() {
@ -42,7 +42,7 @@ public class Session extends AbstractDb implements Closeable {
* 创建会话
*
* @param group 分组
* @return {@link Session}
* @return Session
* @since 4.0.11
*/
public static Session create(String group) {
@ -53,7 +53,7 @@ public class Session extends AbstractDb implements Closeable {
* 创建会话
*
* @param ds 数据源
* @return {@link Session}
* @return Session
*/
public static Session create(DataSource ds) {
return new Session(ds);

View File

@ -1,26 +1,21 @@
package cn.hutool.db;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.dialect.Dialect;
import cn.hutool.db.dialect.DialectFactory;
import cn.hutool.db.handler.EntityListHandler;
import cn.hutool.db.handler.HandleHelper;
import cn.hutool.db.handler.NumberHandler;
import cn.hutool.db.handler.PageResultHandler;
import cn.hutool.db.handler.RsHandler;
import cn.hutool.db.sql.Condition.LikeType;
import cn.hutool.db.sql.Query;
import cn.hutool.db.sql.SqlExecutor;
import cn.hutool.db.sql.SqlBuilder;
import cn.hutool.db.sql.SqlUtil;
import cn.hutool.db.sql.Wrapper;
import javax.sql.DataSource;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
@ -32,15 +27,9 @@ import java.util.List;
*
* @author Luxiaolei
*/
public class SqlConnRunner implements Serializable {
public class SqlConnRunner extends DialectRunner {
private static final long serialVersionUID = 1L;
private Dialect dialect;
/**
* 是否大小写不敏感默认大小写不敏感
*/
protected boolean caseInsensitive = GlobalDbConfig.caseInsensitive;
/**
* 实例化一个新的SQL运行对象
*
@ -79,7 +68,7 @@ public class SqlConnRunner implements Serializable {
* @param dialect 方言
*/
public SqlConnRunner(Dialect dialect) {
this.dialect = dialect;
super(dialect);
}
/**
@ -88,35 +77,12 @@ public class SqlConnRunner implements Serializable {
* @param driverClassName 驱动类名用于识别方言
*/
public SqlConnRunner(String driverClassName) {
this(DialectFactory.newDialect(driverClassName));
super(driverClassName);
}
//------------------------------------------------------- Constructor end
//---------------------------------------------------------------------------- CRUD start
/**
* 插入数据<br>
* 此方法不会关闭Connection
*
* @param conn 数据库连接
* @param record 记录
* @return 插入行数
* @throws SQLException SQL执行异常
*/
public int insert(Connection conn, Entity record) throws SQLException {
checkConn(conn);
if (CollUtil.isEmpty(record)) {
throw new SQLException("Empty entity provided!");
}
PreparedStatement ps = null;
try {
ps = dialect.psForInsert(conn, record);
return ps.executeUpdate();
} finally {
DbUtil.close(ps);
}
}
/**
* 插入或更新数据<br>
* 此方法不会关闭Connection
@ -152,33 +118,16 @@ public class SqlConnRunner implements Serializable {
}
/**
* 批量插入数据<br>
* 批量插入必须严格保持Entity的结构一致不一致会导致插入数据出现不可预知的结果<br>
* 插入数据<br>
* 此方法不会关闭Connection
*
* @param conn 数据库连接
* @param records 记录列表记录KV必须严格一致
* @param record 记录
* @return 插入行数
* @throws SQLException SQL执行异常
*/
public int[] insert(Connection conn, Entity... records) throws SQLException {
checkConn(conn);
if (ArrayUtil.isEmpty(records)) {
return new int[]{0};
}
//单条单独处理
if (1 == records.length) {
return new int[]{insert(conn, records[0])};
}
PreparedStatement ps = null;
try {
ps = dialect.psForInsertBatch(conn, records);
return ps.executeBatch();
} finally {
DbUtil.close(ps);
}
public int insert(Connection conn, Entity record) throws SQLException {
return insert(conn, new Entity[]{record})[0];
}
/**
@ -191,19 +140,7 @@ public class SqlConnRunner implements Serializable {
* @throws SQLException SQL执行异常
*/
public List<Object> insertForGeneratedKeys(Connection conn, Entity record) throws SQLException {
checkConn(conn);
if (CollUtil.isEmpty(record)) {
throw new SQLException("Empty entity provided!");
}
PreparedStatement ps = null;
try {
ps = dialect.psForInsert(conn, record);
ps.executeUpdate();
return StatementUtil.getGeneratedKeys(ps);
} finally {
DbUtil.close(ps);
}
return insert(conn, record, HandleHelper::handleRowToList);
}
/**
@ -216,106 +153,17 @@ public class SqlConnRunner implements Serializable {
* @throws SQLException SQL执行异常
*/
public Long insertForGeneratedKey(Connection conn, Entity record) throws SQLException {
checkConn(conn);
if (CollUtil.isEmpty(record)) {
throw new SQLException("Empty entity provided!");
}
PreparedStatement ps = null;
return insert(conn, record, (rs)->{
Long generatedKey = null;
if (rs != null && rs.next()) {
try {
ps = dialect.psForInsert(conn, record);
ps.executeUpdate();
return StatementUtil.getGeneratedKeyOfLong(ps);
} finally {
DbUtil.close(ps);
generatedKey = rs.getLong(1);
} catch (SQLException e) {
// 自增主键不为数字或者为Oracle的rowid跳过
}
}
/**
* 删除数据<br>
* 此方法不会关闭Connection
*
* @param conn 数据库连接
* @param where 条件
* @return 影响行数
* @throws SQLException SQL执行异常
*/
public int del(Connection conn, Entity where) throws SQLException {
checkConn(conn);
if (CollUtil.isEmpty(where)) {
//不允许做全表删除
throw new SQLException("Empty entity provided!");
}
final Query query = new Query(SqlUtil.buildConditions(where), where.getTableName());
PreparedStatement ps = null;
try {
ps = dialect.psForDelete(conn, query);
return ps.executeUpdate();
} finally {
DbUtil.close(ps);
}
}
/**
* 更新数据<br>
* 此方法不会关闭Connection
*
* @param conn 数据库连接
* @param record 记录
* @param where 条件
* @return 影响行数
* @throws SQLException SQL执行异常
*/
public int update(Connection conn, Entity record, Entity where) throws SQLException {
checkConn(conn);
if (CollUtil.isEmpty(record)) {
throw new SQLException("Empty entity provided!");
}
if (CollUtil.isEmpty(where)) {
//不允许做全表更新
throw new SQLException("Empty where provided!");
}
//表名可以从被更新记录的Entity中获得也可以从Where中获得
String tableName = record.getTableName();
if (StrUtil.isBlank(tableName)) {
tableName = where.getTableName();
record.setTableName(tableName);
}
final Query query = new Query(SqlUtil.buildConditions(where), tableName);
PreparedStatement ps = null;
try {
ps = dialect.psForUpdate(conn, record, query);
return ps.executeUpdate();
} finally {
DbUtil.close(ps);
}
}
/**
* 查询<br>
* 此方法不会关闭Connection
*
* @param <T> 结果对象类型
* @param conn 数据库连接对象
* @param query {@link Query}
* @param rsh 结果集处理对象
* @return 结果对象
* @throws SQLException SQL执行异常
*/
public <T> T find(Connection conn, Query query, RsHandler<T> rsh) throws SQLException {
checkConn(conn);
Assert.notNull(query, "[query] is null !");
PreparedStatement ps = null;
try {
ps = dialect.psForFind(conn, query);
return SqlExecutor.query(ps, rsh);
} finally {
DbUtil.close(ps);
}
return generatedKey;
});
}
/**
@ -331,9 +179,7 @@ public class SqlConnRunner implements Serializable {
* @throws SQLException SQL执行异常
*/
public <T> T find(Connection conn, Collection<String> fields, Entity where, RsHandler<T> rsh) throws SQLException {
final Query query = new Query(SqlUtil.buildConditions(where), where.getTableName());
query.setFields(fields);
return find(conn, query, rsh);
return find(conn, Query.of(where).setFields(fields), rsh);
}
/**
@ -433,24 +279,16 @@ public class SqlConnRunner implements Serializable {
}
/**
* 结果的条目数
* 获取查询结果总数生成类似于 SELECT count(1) from (sql)
*
* @param conn 数据库连接对象
* @param where 查询条件
* @return 复合条件的结果数
* @throws SQLException SQL执行异常
* @param selectSql 查询语句
* @return 结果数
* @throws SQLException SQL异常
*/
public int count(Connection conn, Entity where) throws SQLException {
checkConn(conn);
final Query query = new Query(SqlUtil.buildConditions(where), where.getTableName());
PreparedStatement ps = null;
try {
ps = dialect.psForCount(conn, query);
return SqlExecutor.query(ps, new NumberHandler()).intValue();
} finally {
DbUtil.close(ps);
}
public long count(Connection conn, CharSequence selectSql) throws SQLException {
SqlBuilder sqlBuilder = SqlBuilder.of(selectSql).insertPreFragment("SELECT count(1) from(").append(")");
return page(conn, sqlBuilder, null, new NumberHandler()).intValue();
}
/**
@ -468,32 +306,25 @@ public class SqlConnRunner implements Serializable {
* @throws SQLException SQL执行异常
*/
public <T> T page(Connection conn, Collection<String> fields, Entity where, int pageNumber, int numPerPage, RsHandler<T> rsh) throws SQLException {
return page(conn, fields, where, new Page(pageNumber, numPerPage), rsh);
return page(conn, Query.of(where).setFields(fields).setPage(new Page(pageNumber, numPerPage)), rsh);
}
/**
* 分页查询<br>
* 此方法不会关闭Connection
*
* @param <T> 结果对象类型
* @param conn 数据库连接对象
* @param fields 返回的字段列表null则返回所有字段
* @param where 条件实体类包含表名
* @param sqlBuilder SQL构建器可以使用{@link SqlBuilder#of(CharSequence)} 包装普通SQL
* @param page 分页对象
* @param rsh 结果集处理对象
* @return 结果对象
* @throws SQLException SQL执行异常
* @since 5.5.3
*/
public <T> T page(Connection conn, Collection<String> fields, Entity where, Page page, RsHandler<T> rsh) throws SQLException {
checkConn(conn);
if (null == page) {
return this.find(conn, fields, where, rsh);
}
final Query query = new Query(SqlUtil.buildConditions(where), where.getTableName());
query.setFields(fields);
query.setPage(page);
return SqlExecutor.queryAndClosePs(dialect.psForPage(conn, query), rsh);
public PageResult<Entity> page(Connection conn, SqlBuilder sqlBuilder, Page page) throws SQLException {
final PageResultHandler pageResultHandler = new PageResultHandler(
new PageResult<>(page.getPageNumber(), page.getPageSize(), (int)count(conn, sqlBuilder.build())),
this.caseInsensitive);
return page(conn, sqlBuilder, page, pageResultHandler);
}
/**
@ -509,38 +340,7 @@ public class SqlConnRunner implements Serializable {
* @throws SQLException SQL执行异常
*/
public PageResult<Entity> page(Connection conn, Collection<String> fields, Entity where, int page, int numPerPage) throws SQLException {
checkConn(conn);
final int count = count(conn, where);
final PageResultHandler pageResultHandler = new PageResultHandler(new PageResult<>(page, numPerPage, count), this.caseInsensitive);
return this.page(conn, fields, where, page, numPerPage, pageResultHandler);
}
/**
* 分页查询<br>
* 此方法不会关闭Connection
*
* @param conn 数据库连接对象
* @param fields 返回的字段列表null则返回所有字段
* @param where 条件实体类包含表名
* @param page 分页对象
* @return 结果对象
* @throws SQLException SQL执行异常
*/
public PageResult<Entity> page(Connection conn, Collection<String> fields, Entity where, Page page) throws SQLException {
checkConn(conn);
//查询全部
if (null == page) {
List<Entity> entityList = this.find(conn, fields, where, new EntityListHandler(GlobalDbConfig.caseInsensitive));
final PageResult<Entity> pageResult = new PageResult<>(0, entityList.size(), entityList.size());
pageResult.addAll(entityList);
return pageResult;
}
final int count = count(conn, where);
PageResultHandler pageResultHandler = new PageResultHandler(new PageResult<>(page.getPageNumber(), page.getPageSize(), count), this.caseInsensitive);
return this.page(conn, fields, where, page, pageResultHandler);
return page(conn, fields, where, new Page(page, numPerPage));
}
/**
@ -556,68 +356,39 @@ public class SqlConnRunner implements Serializable {
public PageResult<Entity> page(Connection conn, Entity where, Page page) throws SQLException {
return this.page(conn, null, where, page);
}
/**
* 分页查询<br>
* 此方法不会关闭Connection
*
* @param conn 数据库连接对象
* @param fields 返回的字段列表null则返回所有字段
* @param where 条件实体类包含表名
* @param page 分页对象
* @return 结果对象
* @throws SQLException SQL执行异常
*/
public PageResult<Entity> page(Connection conn, Collection<String> fields, Entity where, Page page) throws SQLException {
final PageResultHandler pageResultHandler = new PageResultHandler(
new PageResult<>(page.getPageNumber(), page.getPageSize(), (int)count(conn, where)),
this.caseInsensitive);
return page(conn, fields, where, page, pageResultHandler);
}
/**
* 分页查询<br>
* 此方法不会关闭Connection
*
* @param conn 数据库连接对象
* @param fields 返回的字段列表null则返回所有字段
* @param where 条件实体类包含表名
* @param page 分页对象
* @param handler 结果集处理器
* @return 结果对象
* @throws SQLException SQL执行异常
*/
public <T> T page(Connection conn, Collection<String> fields, Entity where, Page page, RsHandler<T> handler) throws SQLException {
return this.page(conn, Query.of(where).setFields(fields).setPage(page), handler);
}
//---------------------------------------------------------------------------- CRUD end
//---------------------------------------------------------------------------- Getters and Setters end
/**
* 设置是否在结果中忽略大小写<br>
* 如果忽略则在Entity中调用getXXX时字段值忽略大小写默认忽略
*
* @param caseInsensitive 否在结果中忽略大小写
* @since 5.2.4
*/
public void setCaseInsensitive(boolean caseInsensitive) {
this.caseInsensitive = caseInsensitive;
}
/**
* @return SQL方言
*/
public Dialect getDialect() {
return dialect;
}
/**
* 设置SQL方言
*
* @param dialect 方言
* @return this
*/
public SqlConnRunner setDialect(Dialect dialect) {
this.dialect = dialect;
return this;
}
/**
* 设置包装器包装器用于对表名字段名进行符号包装例如双引号防止关键字与这些表名或字段冲突
*
* @param wrapperChar 包装字符字符会在SQL生成时位于表名和字段名两边null时表示取消包装
* @return this
* @since 4.0.0
*/
public SqlConnRunner setWrapper(Character wrapperChar) {
return setWrapper(new Wrapper(wrapperChar));
}
/**
* 设置包装器包装器用于对表名字段名进行符号包装例如双引号防止关键字与这些表名或字段冲突
*
* @param wrapper 包装器null表示取消包装
* @return this
* @since 4.0.0
*/
public SqlConnRunner setWrapper(Wrapper wrapper) {
this.dialect.setWrapper(wrapper);
return this;
}
//---------------------------------------------------------------------------- Getters and Setters end
//---------------------------------------------------------------------------- Private method start
private void checkConn(Connection conn) {
if (null == conn) {
throw new NullPointerException("Connection object is null!");
}
}
//---------------------------------------------------------------------------- Private method start
}

View File

@ -6,6 +6,8 @@ import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.handler.HandleHelper;
import cn.hutool.db.handler.RsHandler;
import cn.hutool.db.sql.NamedSql;
import cn.hutool.db.sql.SqlBuilder;
import cn.hutool.db.sql.SqlLog;
@ -21,7 +23,6 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@ -230,14 +231,14 @@ public class StatementUtil {
/**
* 获得自增键的值<br>
* 此方法对于Oracle无效
* 此方法对于Oracle无效返回null
*
* @param ps PreparedStatement
* @return 自增键的值
* @return 自增键的值不存在返回null
* @throws SQLException SQL执行异常
*/
public static Long getGeneratedKeyOfLong(Statement ps) throws SQLException {
try (final ResultSet rs = ps.getGeneratedKeys()) {
return getGeneratedKeys(ps, (rs)->{
Long generatedKey = null;
if (rs != null && rs.next()) {
try {
@ -247,7 +248,7 @@ public class StatementUtil {
}
}
return generatedKey;
}
});
}
/**
@ -258,15 +259,21 @@ public class StatementUtil {
* @throws SQLException SQL执行异常
*/
public static List<Object> getGeneratedKeys(Statement ps) throws SQLException {
final List<Object> keys = new ArrayList<>();
try (final ResultSet rs = ps.getGeneratedKeys()) {
if (null != rs) {
int i = 1;
while (rs.next()) {
keys.add(rs.getObject(i++));
return getGeneratedKeys(ps, HandleHelper::handleRowToList);
}
}
return keys;
/**
* 获取主键并使用{@link RsHandler} 处理后返回
* @param statement {@link Statement}
* @param rsHandler 主键结果集处理器
* @param <T> 自定义主键类型
* @return 主键
* @throws SQLException SQL执行异常
* @since 5.5.3
*/
public static <T> T getGeneratedKeys(Statement statement, RsHandler<T> rsHandler) throws SQLException {
try (final ResultSet rs = statement.getGeneratedKeys()) {
return rsHandler.handle(rs);
}
}

View File

@ -1,23 +1,26 @@
package cn.hutool.db.dialect;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.db.Entity;
import cn.hutool.db.Page;
import cn.hutool.db.sql.Order;
import cn.hutool.db.sql.Query;
import cn.hutool.db.sql.SqlBuilder;
import cn.hutool.db.sql.Wrapper;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import cn.hutool.db.Entity;
import cn.hutool.db.sql.Query;
import cn.hutool.db.sql.Wrapper;
/**
* SQL方言不同的数据库由于在某些SQL上有所区别故为每种数据库配置不同的方言<br>
* 由于不同数据库间SQL语句的差异导致无法统一拼接SQL<br>
* Dialect接口旨在根据不同的数据库使用不同的方言实现类来拼接对应的SQL并将SQL和参数放入PreparedStatement中
*
* @author loolly
*
*/
public interface Dialect extends Serializable{
public interface Dialect extends Serializable {
/**
* @return 包装器
@ -32,6 +35,7 @@ public interface Dialect extends Serializable{
void setWrapper(Wrapper wrapper);
// -------------------------------------------- Execute
/**
* 构建用于插入的PreparedStatement
*
@ -74,6 +78,7 @@ public interface Dialect extends Serializable{
PreparedStatement psForUpdate(Connection conn, Entity entity, Query query) throws SQLException;
// -------------------------------------------- Query
/**
* 构建用于获取多条记录的PreparedStatement
*
@ -94,6 +99,20 @@ public interface Dialect extends Serializable{
*/
PreparedStatement psForPage(Connection conn, Query query) throws SQLException;
/**
* 构建用于分页查询的PreparedStatement<br>
* 可以在此方法中使用{@link SqlBuilder#orderBy(Order...)}方法加入排序信息
* 排序信息通过{@link Page#getOrders()}获取
*
* @param conn 数据库连接对象
* @param sqlBuilder SQL构建器可以使用{@link SqlBuilder#of(CharSequence)} 包装普通SQL
* @param page 分页对象
* @return PreparedStatement
* @throws SQLException SQL执行异常
* @since 5.5.3
*/
PreparedStatement psForPage(Connection conn, SqlBuilder sqlBuilder, Page page) throws SQLException;
/**
* 构建用于查询行数的PreparedStatement
*
@ -102,12 +121,16 @@ public interface Dialect extends Serializable{
* @return PreparedStatement
* @throws SQLException SQL执行异常
*/
PreparedStatement psForCount(Connection conn, Query query) throws SQLException;
default PreparedStatement psForCount(Connection conn, Query query) throws SQLException{
query.setFields(ListUtil.toList("count(1)"));
return psForFind(conn, query);
}
/**
* 方言名
*
* @return 方言名
* @since 5.5.3
*/
DialectName dialectName();
String dialectName();
}

View File

@ -1,6 +1,5 @@
package cn.hutool.db.dialect.impl;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
@ -59,7 +58,7 @@ public class AnsiSqlDialect implements Dialect {
@Override
public PreparedStatement psForDelete(Connection conn, Query query) throws SQLException {
Assert.notNull(query, "query must not be null !");
Assert.notNull(query, "query must be not null !");
final Condition[] where = query.getWhere();
if (ArrayUtil.isEmpty(where)) {
@ -73,7 +72,7 @@ public class AnsiSqlDialect implements Dialect {
@Override
public PreparedStatement psForUpdate(Connection conn, Entity entity, Query query) throws SQLException {
Assert.notNull(query, "query must not be null !");
Assert.notNull(query, "query must be not null !");
final Condition[] where = query.getWhere();
if (ArrayUtil.isEmpty(where)) {
@ -88,32 +87,27 @@ public class AnsiSqlDialect implements Dialect {
@Override
public PreparedStatement psForFind(Connection conn, Query query) throws SQLException {
Assert.notNull(query, "query must not be null !");
final SqlBuilder find = SqlBuilder.create(wrapper).query(query);
return StatementUtil.prepareStatement(conn, find);
return psForPage(conn, query);
}
@Override
public PreparedStatement psForPage(Connection conn, Query query) throws SQLException {
// 验证
if (query == null || StrUtil.hasBlank(query.getTableNames())) {
throw new DbRuntimeException("Table name must not be null !");
Assert.notNull(query, "query must be not null !");
if (StrUtil.hasBlank(query.getTableNames())) {
throw new DbRuntimeException("Table name must be not empty !");
}
final Page page = query.getPage();
if (null == page) {
// 无分页信息默认使用find
return this.psForFind(conn, query);
final SqlBuilder find = SqlBuilder.create(wrapper).query(query);
return psForPage(conn, find, query.getPage());
}
SqlBuilder find = SqlBuilder.create(wrapper).query(query).orderBy(page.getOrders());
@Override
public PreparedStatement psForPage(Connection conn, SqlBuilder sqlBuilder, Page page) throws SQLException {
// 根据不同数据库在查询SQL语句基础上包装其分页的语句
find = wrapPageSql(find, page);
return StatementUtil.prepareStatement(conn, find);
if(null != page){
sqlBuilder = wrapPageSql(sqlBuilder.orderBy(page.getOrders()), page);
}
return StatementUtil.prepareStatement(conn, sqlBuilder);
}
/**
@ -127,18 +121,16 @@ public class AnsiSqlDialect implements Dialect {
*/
protected SqlBuilder wrapPageSql(SqlBuilder find, Page page) {
// limit A offset B 表示A就是你需要多少行B就是查询的起点位置
return find.append(" limit ").append(page.getPageSize()).append(" offset ").append(page.getStartPosition());
return find
.append(" limit ")
.append(page.getPageSize())
.append(" offset ")
.append(page.getStartPosition());
}
@Override
public PreparedStatement psForCount(Connection conn, Query query) throws SQLException {
query.setFields(ListUtil.toList("count(1)"));
return psForFind(conn, query);
}
@Override
public DialectName dialectName() {
return DialectName.ANSI;
public String dialectName() {
return DialectName.ANSI.name();
}
// ---------------------------------------------------------------------------- Protected method start

View File

@ -17,8 +17,8 @@ public class H2Dialect extends AnsiSqlDialect {
}
@Override
public DialectName dialectName() {
return DialectName.H2;
public String dialectName() {
return DialectName.H2.name();
}
@Override

View File

@ -23,7 +23,7 @@ public class MysqlDialect extends AnsiSqlDialect{
}
@Override
public DialectName dialectName() {
return DialectName.MYSQL;
public String dialectName() {
return DialectName.MYSQL.toString();
}
}

View File

@ -13,7 +13,8 @@ public class OracleDialect extends AnsiSqlDialect{
private static final long serialVersionUID = 6122761762247483015L;
public OracleDialect() {
// wrapper = new Wrapper('"'); //Oracle所有字段名用双引号包围防止字段名或表名与系统关键字冲突
//Oracle所有字段名用双引号包围防止字段名或表名与系统关键字冲突
//wrapper = new Wrapper('"');
}
@Override
@ -27,7 +28,7 @@ public class OracleDialect extends AnsiSqlDialect{
}
@Override
public DialectName dialectName() {
return DialectName.ORACLE;
public String dialectName() {
return DialectName.ORACLE.name();
}
}

View File

@ -17,7 +17,7 @@ public class PostgresqlDialect extends AnsiSqlDialect{
}
@Override
public DialectName dialectName() {
return DialectName.POSTGREESQL;
public String dialectName() {
return DialectName.POSTGREESQL.name();
}
}

View File

@ -34,7 +34,7 @@ public class SqlServer2012Dialect extends AnsiSqlDialect {
}
@Override
public DialectName dialectName() {
return DialectName.SQLSERVER2012;
public String dialectName() {
return DialectName.SQLSERVER2012.name();
}
}

View File

@ -16,7 +16,7 @@ public class Sqlite3Dialect extends AnsiSqlDialect{
}
@Override
public DialectName dialectName() {
return DialectName.SQLITE3;
public String dialectName() {
return DialectName.SQLITE3.name();
}
}

View File

@ -21,6 +21,6 @@ public class NumberHandler implements RsHandler<Number>{
@Override
public Number handle(ResultSet rs) throws SQLException {
return rs.next() ? rs.getBigDecimal(1) : null;
return (null != rs && rs.next()) ? rs.getBigDecimal(1) : null;
}
}

View File

@ -19,6 +19,7 @@ import java.sql.SQLException;
* @author Luxiaolei
*
*/
@FunctionalInterface
public interface RsHandler<T> extends Serializable{
/**

View File

@ -1,12 +1,13 @@
package cn.hutool.db.sql;
import java.util.Collection;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.db.DbRuntimeException;
import cn.hutool.db.Entity;
import cn.hutool.db.Page;
import java.util.Collection;
/**
* 查询对象用于传递查询所需的字段值<br>
* 查询对象根据表名可以多个多个条件 {@link Condition} 构建查询对象完成查询<br>
@ -26,6 +27,16 @@ public class Query {
/** 分页对象 */
Page page;
/**
* {@link Entity}构建Query
* @param where 条件查询{@link Entity}包含条件Map和表名
* @return Query
* @since 5.5.3
*/
public static Query of(Entity where){
return new Query(SqlUtil.buildConditions(where), where.getTableName());
}
// --------------------------------------------------------------- Constructor start
/**
* 构造
@ -147,9 +158,9 @@ public class Query {
}
/**
* 获得分页对象
* 获得分页对象无分页返回{@code null}
*
* @return 分页对象
* @return 分页对象 or {@code null}
*/
public Page getPage() {
return page;

View File

@ -3,7 +3,6 @@ package cn.hutool.db.sql;
import cn.hutool.core.builder.Builder;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.DbRuntimeException;
import cn.hutool.db.Entity;
@ -12,6 +11,7 @@ import cn.hutool.db.dialect.DialectName;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
@ -46,6 +46,16 @@ public class SqlBuilder implements Builder<String>{
return new SqlBuilder(wrapper);
}
/**
* 从已有的SQL中构建一个SqlBuilder
* @param sql SQL语句
* @return SqlBuilder
* @since 5.5.3
*/
public static SqlBuilder of(CharSequence sql){
return create().append(sql);
}
// --------------------------------------------------------------- Static methods end
// --------------------------------------------------------------- Enums start
@ -105,6 +115,19 @@ public class SqlBuilder implements Builder<String>{
* @return 自己
*/
public SqlBuilder insert(Entity entity, DialectName dialectName) {
return insert(entity, dialectName.name());
}
/**
* 插入<br>
* 插入会忽略空的字段名及其对应值但是对于有字段名对应值为{@code null}的情况不忽略
*
* @param entity 实体
* @param dialectName 方言名用于对特殊数据库特殊处理
* @return 自己
* @since 5.5.3
*/
public SqlBuilder insert(Entity entity, String dialectName) {
// 验证
validateEntity(entity);
@ -114,7 +137,7 @@ public class SqlBuilder implements Builder<String>{
entity.setTableName(wrapper.wrap(entity.getTableName()));
}
final boolean isOracle = ObjectUtil.equal(dialectName, DialectName.ORACLE);// 对Oracle的特殊处理
final boolean isOracle = StrUtil.equalsAnyIgnoreCase(dialectName, DialectName.ORACLE.name());// 对Oracle的特殊处理
final StringBuilder fieldsPart = new StringBuilder();
final StringBuilder placeHolder = new StringBuilder();
@ -519,7 +542,14 @@ public class SqlBuilder implements Builder<String>{
}
/**
* 追加SQL其它部分片段
* 追加SQL其它部分片段此方法只是简单的追加SQL字符串空格需手动加入例如
*
* <pre>
* SqlBuilder builder = SqlBuilder.of("select *");
* builder.append(" from ").append("user");
* </pre>
*
* 如果需要追加带占位符的片段需调用{@link #addParams(Object...)} 方法加入对应参数值
*
* @param sqlFragment SQL其它部分片段
* @return this
@ -531,6 +561,26 @@ public class SqlBuilder implements Builder<String>{
return this;
}
/**
* 手动增加参数调用此方法前需确认SQL中有对应占位符主要用于自定义SQL片段中有占位符的情况例如
*
* <pre>
* SqlBuilder builder = SqlBuilder.of("select * from user where id=?");
* builder.append(" and name=?")
* builder.addParams(1, "looly");
* </pre>
*
* @param params 参数列表
* @return this
* @since 5.5.3
*/
public SqlBuilder addParams(Object... params){
if(ArrayUtil.isNotEmpty(params)){
Collections.addAll(this.paramValues, params);
}
return this;
}
/**
* 构建查询SQL
*

View File

@ -259,6 +259,22 @@ public class SqlExecutor {
}
}
/**
* 执行查询语句<br>
* 此方法不会关闭Connection
*
* @param <T> 处理结果类型
* @param conn 数据库连接对象
* @param sqlBuilder SQL构建器包含参数
* @param rsh 结果集处理对象
* @return 结果对象
* @throws SQLException SQL执行异常
* @since 5.5.3
*/
public static <T> T query(Connection conn, SqlBuilder sqlBuilder, RsHandler<T> rsh) throws SQLException {
return query(conn, sqlBuilder.build(), rsh, sqlBuilder.getParamValueArray());
}
// -------------------------------------------------------------------------------------- Execute With PreparedStatement
/**
* 用于执行 INSERTUPDATE DELETE 语句以及 SQL DDL数据定义语言语句例如 CREATE TABLE DROP TABLE<br>

View File

@ -4,6 +4,7 @@ import java.util.*;
/**
* SQL格式化器 from Hibernate
*
* @author looly
*/
public class SqlFormatter {
@ -64,7 +65,7 @@ public class SqlFormatter {
boolean beginLine = true;
boolean afterBeginBeforeEnd = false;
boolean afterByOrSetOrFromOrSelect = false;
// boolean afterValues = false;
// boolean afterValues = false;
boolean afterOn = false;
boolean afterBetween = false;
boolean afterInsert = false;

View File

@ -1,5 +1,6 @@
package cn.hutool.db.sql;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Editor;
import cn.hutool.core.util.ArrayUtil;
@ -98,7 +99,7 @@ public class Wrapper {
//对于Oracle这类数据库表名中包含用户名需要单独拆分包装
if(field.contains(StrUtil.DOT)){
final Collection<String> target = CollectionUtil.filter(StrUtil.split(field, StrUtil.C_DOT), (Editor<String>) t -> StrUtil.format("{}{}{}", preWrapQuote, t, sufWrapQuote));
final Collection<String> target = CollUtil.filter(StrUtil.split(field, StrUtil.C_DOT, 2), (Editor<String>) t -> StrUtil.format("{}{}{}", preWrapQuote, t, sufWrapQuote));
return CollectionUtil.join(target, StrUtil.DOT);
}

View File

@ -37,6 +37,25 @@ public class DbTest {
Assert.assertEquals(1, page1.size());
}
@Test
public void pageTest2() throws SQLException {
String sql = "select * from user";
// 测试数据库中一共4条数据第0页有3条第1页有1条
List<Entity> page0 = Db.use().page(
sql, Page.of(0, 3));
Assert.assertEquals(3, page0.size());
List<Entity> page1 = Db.use().page(
sql, Page.of(1, 3));
Assert.assertEquals(1, page1.size());
}
@Test
public void countTest() throws SQLException {
final long count = Db.use().count("select * from user");
Assert.assertEquals(4, count);
}
@Test
public void findLikeTest() throws SQLException {
// 方式1

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</parent>
<artifactId>hutool-dfa</artifactId>

View File

@ -0,0 +1,61 @@
package cn.hutool.dfa;
import cn.hutool.core.lang.DefaultSegment;
/**
* <p>
* 匹配到的单词包含单词text中匹配单词的内容以及匹配内容在text中的下标
* 下标可以用来做单词的进一步处理如果替换成**
*
* @author 肖海斌
*/
public class FoundWord extends DefaultSegment<Integer> {
/**
* 生效的单词即单词树中的词
*/
private final String word;
/**
* 单词匹配到的内容即文中的单词
*/
private final String foundWord;
/**
* 构造
*
* @param word 生效的单词即单词树中的词
* @param foundWord 单词匹配到的内容即文中的单词
* @param startIndex 起始位置包含
* @param endIndex 结束位置包含
*/
public FoundWord(String word, String foundWord, int startIndex, int endIndex) {
super(startIndex, endIndex);
this.word = word;
this.foundWord = foundWord;
}
/**
* 获取生效的单词即单词树中的词
*
* @return 生效的单词
*/
public String getWord() {
return word;
}
/**
* 获取单词匹配到的内容即文中的单词
* @return 单词匹配到的内容
*/
public String getFoundWord() {
return foundWord;
}
/**
* 默认的只输出匹配到的关键字
* @return 匹配到的关键字
*/
@Override
public String toString() {
return this.foundWord;
}
}

View File

@ -0,0 +1,22 @@
package cn.hutool.dfa;
/**
* @author 肖海斌
* 敏感词过滤处理器默认按字符数替换成*
*/
public interface SensitiveProcessor {
/**
* 敏感词过滤处理
* @param foundWord 敏感词匹配到的内容
* @return 敏感词过滤后的内容默认按字符数替换成*
*/
default String process(FoundWord foundWord) {
int length = foundWord.getFoundWord().length();
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append("*");
}
return sb.toString();
}
}

View File

@ -1,17 +1,20 @@
package cn.hutool.dfa;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 敏感词工具类
* @author Looly
*
* @author Looly
*/
public final class SensitiveUtil {
@ -21,31 +24,33 @@ public final class SensitiveUtil {
/**
* @return 是否已经被初始化
*/
public static boolean isInited(){
return !sensitiveTree.isEmpty();
public static boolean isInited() {
return false == sensitiveTree.isEmpty();
}
/**
* 初始化敏感词树
*
* @param isAsync 是否异步初始化
* @param sensitiveWords 敏感词列表
*/
public static void init(final Collection<String> sensitiveWords, boolean isAsync){
if(isAsync){
public static void init(final Collection<String> sensitiveWords, boolean isAsync) {
if (isAsync) {
ThreadUtil.execAsync(() -> {
init(sensitiveWords);
return true;
});
}else{
} else {
init(sensitiveWords);
}
}
/**
* 初始化敏感词树
*
* @param sensitiveWords 敏感词列表
*/
public static void init(Collection<String> sensitiveWords){
public static void init(Collection<String> sensitiveWords) {
sensitiveTree.clear();
sensitiveTree.addWords(sensitiveWords);
// log.debug("Sensitive init finished, sensitives: {}", sensitiveWords);
@ -53,22 +58,24 @@ public final class SensitiveUtil {
/**
* 初始化敏感词树
*
* @param sensitiveWords 敏感词列表组成的字符串
* @param isAsync 是否异步初始化
* @param separator 分隔符
*/
public static void init(String sensitiveWords, char separator, boolean isAsync){
if(StrUtil.isNotBlank(sensitiveWords)){
public static void init(String sensitiveWords, char separator, boolean isAsync) {
if (StrUtil.isNotBlank(sensitiveWords)) {
init(StrUtil.split(sensitiveWords, separator), isAsync);
}
}
/**
* 初始化敏感词树使用逗号分隔每个单词
*
* @param sensitiveWords 敏感词列表组成的字符串
* @param isAsync 是否异步初始化
*/
public static void init(String sensitiveWords, boolean isAsync){
public static void init(String sensitiveWords, boolean isAsync) {
init(sensitiveWords, DEFAULT_SEPARATOR, isAsync);
}
@ -80,56 +87,115 @@ public final class SensitiveUtil {
* @since 5.4.4
*/
public static void setCharFilter(Filter<Character> charFilter) {
if(charFilter != null) {
if (charFilter != null) {
sensitiveTree.setCharFilter(charFilter);
}
}
/**
* 是否包含敏感词
*
* @param text 文本
* @return 是否包含
*/
public static boolean containsSensitive(String text){
public static boolean containsSensitive(String text) {
return sensitiveTree.isMatch(text);
}
/**
* 是否包含敏感词
*
* @param obj bean会被转为JSON字符串
* @return 是否包含
*/
public static boolean containsSensitive(Object obj){
public static boolean containsSensitive(Object obj) {
return sensitiveTree.isMatch(JSONUtil.toJsonStr(obj));
}
/**
* 查找敏感词返回找到的第一个敏感词
*
* @param text 文本
* @return 敏感词
* @deprecated 请使用 {@link #getFoundFirstSensitive(String)}
*/
public static String getFindedFirstSensitive(String text){
@Deprecated
public static String getFindedFirstSensitive(String text) {
return sensitiveTree.match(text);
}
/**
* 查找敏感词返回找到的第一个敏感词
*
* @param text 文本
* @return 敏感词
* @since 5.5.3
*/
public static FoundWord getFoundFirstSensitive(String text) {
return sensitiveTree.matchWord(text);
}
/**
* 查找敏感词返回找到的第一个敏感词
*
* @param obj bean会被转为JSON字符串
* @return 敏感词
* @deprecated 请使用 {@link #getFoundFirstSensitive(Object)}
*/
public static String getFindedFirstSensitive(Object obj){
@Deprecated
public static String getFindedFirstSensitive(Object obj) {
return sensitiveTree.match(JSONUtil.toJsonStr(obj));
}
/**
* 查找敏感词返回找到的所有敏感词
* @param text 文本
* 查找敏感词返回找到的第一个敏感词
*
* @param obj bean会被转为JSON字符串
* @return 敏感词
*/
public static List<String> getFindedAllSensitive(String text){
public static FoundWord getFoundFirstSensitive(Object obj) {
return sensitiveTree.matchWord(JSONUtil.toJsonStr(obj));
}
/**
* 查找敏感词返回找到的所有敏感词
*
* @param text 文本
* @return 敏感词
* @deprecated 请使用 {@link #getFoundAllSensitive(String)}
*/
@Deprecated
public static List<String> getFindedAllSensitive(String text) {
return sensitiveTree.matchAll(text);
}
/**
* 查找敏感词返回找到的所有敏感词
*
* @param text 文本
* @return 敏感词
* @since 5.5.3
*/
public static List<FoundWord> getFoundAllSensitive(String text) {
return sensitiveTree.matchAllWords(text);
}
/**
* 查找敏感词返回找到的所有敏感词<br>
* 密集匹配原则假如关键词有 ab,b文本是abab将匹配 [ab,b,ab]<br>
* 贪婪匹配最长匹配原则假如关键字a,ab最长匹配将匹配[a, ab]
*
* @param text 文本
* @param isDensityMatch 是否使用密集匹配原则
* @param isGreedMatch 是否使用贪婪匹配最长匹配原则
* @return 敏感词
* @deprecated 请使用 {@link #getFoundAllSensitive(String, boolean, boolean)}
*/
@Deprecated
public static List<String> getFindedAllSensitive(String text, boolean isDensityMatch, boolean isGreedMatch) {
return sensitiveTree.matchAll(text, -1, isDensityMatch, isGreedMatch);
}
/**
* 查找敏感词返回找到的所有敏感词<br>
* 密集匹配原则假如关键词有 ab,b文本是abab将匹配 [ab,b,ab]<br>
@ -140,19 +206,33 @@ public final class SensitiveUtil {
* @param isGreedMatch 是否使用贪婪匹配最长匹配原则
* @return 敏感词
*/
public static List<String> getFindedAllSensitive(String text, boolean isDensityMatch, boolean isGreedMatch){
return sensitiveTree.matchAll(text, -1, isDensityMatch, isGreedMatch);
public static List<FoundWord> getFoundAllSensitive(String text, boolean isDensityMatch, boolean isGreedMatch) {
return sensitiveTree.matchAllWords(text, -1, isDensityMatch, isGreedMatch);
}
/**
* 查找敏感词返回找到的所有敏感词
*
* @param bean 对象会被转为JSON
* @return 敏感词
* @deprecated 请使用 {@link #getFoundAllSensitive(Object)}
*/
public static List<String> getFindedAllSensitive(Object bean){
@Deprecated
public static List<String> getFindedAllSensitive(Object bean) {
return sensitiveTree.matchAll(JSONUtil.toJsonStr(bean));
}
/**
* 查找敏感词返回找到的所有敏感词
*
* @param bean 对象会被转为JSON
* @return 敏感词
* @since 5.5.3
*/
public static List<FoundWord> getFoundAllSensitive(Object bean) {
return sensitiveTree.matchAllWords(JSONUtil.toJsonStr(bean));
}
/**
* 查找敏感词返回找到的所有敏感词<br>
* 密集匹配原则假如关键词有 ab,b文本是abab将匹配 [ab,b,ab]<br>
@ -162,8 +242,77 @@ public final class SensitiveUtil {
* @param isDensityMatch 是否使用密集匹配原则
* @param isGreedMatch 是否使用贪婪匹配最长匹配原则
* @return 敏感词
* @deprecated 请使用 {@link #getFoundAllSensitive(Object, boolean, boolean)}
*/
public static List<String> getFindedAllSensitive(Object bean, boolean isDensityMatch, boolean isGreedMatch){
return getFindedAllSensitive(JSONUtil.toJsonStr(bean), isDensityMatch, isGreedMatch);
@Deprecated
public static List<String> getFindedAllSensitive(Object bean, boolean isDensityMatch, boolean isGreedMatch) {
return sensitiveTree.matchAll(JSONUtil.toJsonStr(bean), -1, isDensityMatch, isGreedMatch);
}
/**
* 查找敏感词返回找到的所有敏感词<br>
* 密集匹配原则假如关键词有 ab,b文本是abab将匹配 [ab,b,ab]<br>
* 贪婪匹配最长匹配原则假如关键字a,ab最长匹配将匹配[a, ab]
*
* @param bean 对象会被转为JSON
* @param isDensityMatch 是否使用密集匹配原则
* @param isGreedMatch 是否使用贪婪匹配最长匹配原则
* @return 敏感词
* @since 5.5.3
*/
public static List<FoundWord> getFoundAllSensitive(Object bean, boolean isDensityMatch, boolean isGreedMatch) {
return getFoundAllSensitive(JSONUtil.toJsonStr(bean), isDensityMatch, isGreedMatch);
}
/**
* 敏感词过滤
*
* @param bean 对象会被转为JSON
* @param isGreedMatch 贪婪匹配最长匹配原则假如关键字a,ab最长匹配将匹配[a, ab]
* @param sensitiveProcessor 敏感词处理器默认按匹配内容的字符数替换成*
* @param <T> bean的class类型
* @return 敏感词过滤处理后的bean对象
*/
public static <T> T sensitiveFilter(T bean, boolean isGreedMatch, SensitiveProcessor sensitiveProcessor) {
String jsonText = JSONUtil.toJsonStr(bean);
@SuppressWarnings("unchecked")
final Class<T> c = (Class<T>) bean.getClass();
return JSONUtil.toBean(sensitiveFilter(jsonText, isGreedMatch, sensitiveProcessor), c);
}
/**
* 处理过滤文本中的敏感词默认替换成*
*
* @param text 文本
* @param isGreedMatch 贪婪匹配最长匹配原则假如关键字a,ab最长匹配将匹配[a, ab]
* @param sensitiveProcessor 敏感词处理器默认按匹配内容的字符数替换成*
* @return 敏感词过滤处理后的文本
*/
public static String sensitiveFilter(String text, boolean isGreedMatch, SensitiveProcessor sensitiveProcessor) {
if (StrUtil.isEmpty(text)) {
return text;
}
//敏感词过滤场景下不需要密集匹配
List<FoundWord> foundWordList = getFoundAllSensitive(text, false, isGreedMatch);
if (CollUtil.isEmpty(foundWordList)) {
return text;
}
sensitiveProcessor = sensitiveProcessor == null ? new SensitiveProcessor() {
} : sensitiveProcessor;
Map<Integer, FoundWord> foundWordMap = new HashMap<>(foundWordList.size());
foundWordList.forEach(foundWord -> foundWordMap.put(foundWord.getStartIndex(), foundWord));
int length = text.length();
StringBuilder textStringBuilder = new StringBuilder();
for (int i = 0; i < length; i++) {
FoundWord fw = foundWordMap.get(i);
if (fw != null) {
textStringBuilder.append(sensitiveProcessor.process(fw));
i = fw.getEndIndex();
} else {
textStringBuilder.append(text.charAt(i));
}
}
return textStringBuilder.toString();
}
}

View File

@ -1,5 +1,6 @@
package cn.hutool.dfa;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.text.StrBuilder;
@ -31,7 +32,7 @@ public class WordTree extends HashMap<Character, WordTree> {
private static final long serialVersionUID = -4646423269465809276L;
/**
* 敏感词字符末尾标识用于标识单词末尾字符
* 词字符末尾标识用于标识单词末尾字符
*/
private final Set<Character> endCharacterSet = new HashSet<>();
/**
@ -67,26 +68,30 @@ public class WordTree extends HashMap<Character, WordTree> {
* 增加一组单词
*
* @param words 单词集合
* @return this
*/
public void addWords(Collection<String> words) {
public WordTree addWords(Collection<String> words) {
if (false == (words instanceof Set)) {
words = new HashSet<>(words);
}
for (String word : words) {
addWord(word);
}
return this;
}
/**
* 增加一组单词
*
* @param words 单词数组
* @return this
*/
public void addWords(String... words) {
public WordTree addWords(String... words) {
HashSet<String> wordsSet = CollectionUtil.newHashSet(words);
for (String word : wordsSet) {
addWord(word);
}
return this;
}
/**
@ -94,7 +99,7 @@ public class WordTree extends HashMap<Character, WordTree> {
*
* @param word 单词
*/
public void addWord(String word) {
public WordTree addWord(String word) {
final Filter<Character> charFilter = this.charFilter;
WordTree parent = null;
WordTree current = this;
@ -117,8 +122,8 @@ public class WordTree extends HashMap<Character, WordTree> {
if (null != parent) {
parent.setEnd(currentChar);
}
return this;
}
//------------------------------------------------------------------------------- match
/**
@ -131,7 +136,7 @@ public class WordTree extends HashMap<Character, WordTree> {
if (null == text) {
return false;
}
return null != match(text);
return null != matchWord(text);
}
/**
@ -141,14 +146,23 @@ public class WordTree extends HashMap<Character, WordTree> {
* @return 匹配到的关键字
*/
public String match(String text) {
final FoundWord foundWord = matchWord(text);
return null != foundWord ? foundWord.toString() : null;
}
/**
* 获得第一个匹配的关键字
*
* @param text 被检查的文本
* @return 匹配到的关键字
* @since 5.5.3
*/
public FoundWord matchWord(String text) {
if (null == text) {
return null;
}
List<String> matchAll = matchAll(text, 1);
if (CollectionUtil.isNotEmpty(matchAll)) {
return matchAll.get(0);
}
return null;
final List<FoundWord> matchAll = matchAllWords(text, 1);
return CollUtil.get(matchAll, 0);
}
//------------------------------------------------------------------------------- match all
@ -163,6 +177,17 @@ public class WordTree extends HashMap<Character, WordTree> {
return matchAll(text, -1);
}
/**
* 找出所有匹配的关键字
*
* @param text 被检查的文本
* @return 匹配的词列表
* @since 5.5.3
*/
public List<FoundWord> matchAllWords(String text) {
return matchAllWords(text, -1);
}
/**
* 找出所有匹配的关键字
*
@ -174,6 +199,18 @@ public class WordTree extends HashMap<Character, WordTree> {
return matchAll(text, limit, false, false);
}
/**
* 找出所有匹配的关键字
*
* @param text 被检查的文本
* @param limit 限制匹配个数
* @return 匹配的词列表
* @since 5.5.3
*/
public List<FoundWord> matchAllWords(String text, int limit) {
return matchAllWords(text, limit, false, false);
}
/**
* 找出所有匹配的关键字<br>
* 密集匹配原则假如关键词有 ab,b文本是abab将匹配 [ab,b,ab]<br>
@ -186,19 +223,38 @@ public class WordTree extends HashMap<Character, WordTree> {
* @return 匹配的词列表
*/
public List<String> matchAll(String text, int limit, boolean isDensityMatch, boolean isGreedMatch) {
final List<FoundWord> matchAllWords = matchAllWords(text, limit, isDensityMatch, isGreedMatch);
return CollUtil.map(matchAllWords, FoundWord::toString, true);
}
/**
* 找出所有匹配的关键字<br>
* 密集匹配原则假如关键词有 ab,b文本是abab将匹配 [ab,b,ab]<br>
* 贪婪匹配最长匹配原则假如关键字a,ab最长匹配将匹配[a, ab]
*
* @param text 被检查的文本
* @param limit 限制匹配个数
* @param isDensityMatch 是否使用密集匹配原则
* @param isGreedMatch 是否使用贪婪匹配最长匹配原则
* @return 匹配的词列表
* @since 5.5.3
*/
public List<FoundWord> matchAllWords(String text, int limit, boolean isDensityMatch, boolean isGreedMatch) {
if (null == text) {
return null;
}
List<String> foundWords = new ArrayList<>();
List<FoundWord> foundWords = new ArrayList<>();
WordTree current = this;
int length = text.length();
final Filter<Character> charFilter = this.charFilter;
//存放查找到的字符缓存完整出现一个词时加到findedWords中否则清空
final StrBuilder wordBuffer = StrUtil.strBuilder();
final StrBuilder keyBuffer = StrUtil.strBuilder();
char currentChar;
for (int i = 0; i < length; i++) {
wordBuffer.reset();
keyBuffer.reset();
for (int j = i; j < length; j++) {
currentChar = text.charAt(j);
// Console.log("i: {}, j: {}, currentChar: {}", i, j, currentChar);
@ -216,9 +272,10 @@ public class WordTree extends HashMap<Character, WordTree> {
break;
}
wordBuffer.append(currentChar);
keyBuffer.append(currentChar);
if (current.isEnd(currentChar)) {
//到达单词末尾关键词成立从此词的下一个位置开始查找
foundWords.add(wordBuffer.toString());
foundWords.add(new FoundWord(keyBuffer.toString(), wordBuffer.toString(), i, j));
if (limit > 0 && foundWords.size() >= limit) {
//超过匹配限制个数直接返回
return foundWords;
@ -241,8 +298,6 @@ public class WordTree extends HashMap<Character, WordTree> {
}
return foundWords;
}
//--------------------------------------------------------------------------------------- Private method start
/**

View File

@ -1,12 +1,10 @@
package cn.hutool.dfa.test;
import java.util.List;
package cn.hutool.dfa;
import cn.hutool.core.collection.CollUtil;
import org.junit.Assert;
import org.junit.Test;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.dfa.WordTree;
import java.util.List;
/**
* DFA单元测试
@ -29,7 +27,7 @@ public class DfaTest {
// 匹配到就不再继续匹配了因此大土豆不匹配
// 匹配到刚出锅就跳过这三个字了因此出锅不匹配由于刚首先被匹配因此长的被匹配最短匹配只针对第一个字相同选最短
List<String> matchAll = tree.matchAll(text, -1, false, false);
Assert.assertEquals(matchAll, CollectionUtil.newArrayList("", "土^豆", "刚出锅"));
Assert.assertEquals(matchAll, CollUtil.newArrayList("", "土^豆", "刚出锅"));
}
/**
@ -45,7 +43,7 @@ public class DfaTest {
// 被匹配最短匹配原则大土豆被跳过土豆继续被匹配
// 刚出锅被匹配由于不跳过已经匹配的词出锅被匹配
List<String> matchAll = tree.matchAll(text, -1, true, false);
Assert.assertEquals(matchAll, CollectionUtil.newArrayList("", "土^豆", "刚出锅", "出锅"));
Assert.assertEquals(matchAll, CollUtil.newArrayList("", "土^豆", "刚出锅", "出锅"));
}
/**
@ -61,7 +59,7 @@ public class DfaTest {
// 匹配到由于到最长匹配因此大土豆接着被匹配
// 由于大土豆被匹配土豆被跳过由于刚出锅被匹配出锅被跳过
List<String> matchAll = tree.matchAll(text, -1, false, true);
Assert.assertEquals(matchAll, CollectionUtil.newArrayList("", "大土^豆", "刚出锅"));
Assert.assertEquals(matchAll, CollUtil.newArrayList("", "大土^豆", "刚出锅"));
}
@ -78,7 +76,7 @@ public class DfaTest {
// 匹配到由于到最长匹配因此大土豆接着被匹配由于不跳过已经匹配的关键词土豆继续被匹配
// 刚出锅被匹配由于不跳过已经匹配的词出锅被匹配
List<String> matchAll = tree.matchAll(text, -1, true, true);
Assert.assertEquals(matchAll, CollectionUtil.newArrayList("", "大土^豆", "土^豆", "刚出锅", "出锅"));
Assert.assertEquals(matchAll, CollUtil.newArrayList("", "大土^豆", "土^豆", "刚出锅", "出锅"));
}
@ -91,7 +89,7 @@ public class DfaTest {
tree.addWord("tio");
List<String> all = tree.matchAll("AAAAAAAt-ioBBBBBBB");
Assert.assertEquals(all, CollectionUtil.newArrayList("t-io"));
Assert.assertEquals(all, CollUtil.newArrayList("t-io"));
}
@Test

View File

@ -0,0 +1,48 @@
package cn.hutool.dfa;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class SensitiveUtilTest {
@Test
public void testSensitiveFilter() {
List<String> wordList = new ArrayList<>();
wordList.add("");
wordList.add("大土豆");
wordList.add("土豆");
wordList.add("刚出锅");
wordList.add("出锅");
TestBean bean = new TestBean();
bean.setStr("我有一颗$大土^豆,刚出锅的");
bean.setNum(100);
SensitiveUtil.init(wordList);
bean = SensitiveUtil.sensitiveFilter(bean, true, null);
Assert.assertEquals(bean.getStr(), "我有一颗$*******的");
}
public static class TestBean {
private String str;
private Integer num;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
}
}

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</parent>
<artifactId>hutool-extra</artifactId>
@ -130,6 +130,13 @@
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ch.ethz.ganymed</groupId>
<artifactId>ganymed-ssh2</artifactId>
<version>262</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<!-- 二维码 -->
<dependency>
<groupId>com.google.zxing</groupId>

View File

@ -0,0 +1,7 @@
/**
* Spring相关工具封装
*
* @author looly
*
*/
package cn.hutool.extra.spring;

View File

@ -0,0 +1,130 @@
package cn.hutool.extra.ssh;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
/**
* Ganymed-SSH2封装http://www.ganymed.ethz.ch/ssh2/
*
* @author looly
* @since 5.5.3
*/
public class GanymedUtil {
/**
* 连接到服务器
*
* @param sshHost 主机
* @param sshPort 端口
* @return {@link Connection}
*/
public static Connection connect(String sshHost, int sshPort) {
Connection conn = new Connection(sshHost, sshPort);
try {
conn.connect();
} catch (IOException e) {
throw new IORuntimeException(e);
}
return conn;
}
/**
* 打开远程会话
*
* @param sshHost 主机
* @param sshPort 端口
* @param sshUser 用户名如果为null默认root
* @param sshPass 密码
* @return {@link Session}
*/
public static Session openSession(String sshHost, int sshPort, String sshUser, String sshPass) {
// 默认root用户
if (StrUtil.isEmpty(sshUser)) {
sshUser = "root";
}
final Connection connect = connect(sshHost, sshPort);
try {
connect.authenticateWithPassword(sshUser, sshPass);
return connect.openSession();
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/**
* 执行Shell命令使用EXEC方式
* <p>
* 此方法单次发送一个命令到服务端不读取环境变量执行结束后自动关闭Session不会产生阻塞
* </p>
*
* @param session Session会话
* @param cmd 命令
* @param charset 发送和读取内容的编码
* @param errStream 错误信息输出到的位置
*/
public static String exec(Session session, String cmd, Charset charset, OutputStream errStream) {
final String result;
try {
session.execCommand(cmd, charset.name());
result = IoUtil.read(new StreamGobbler(session.getStdout()), charset);
// 错误输出
IoUtil.copy(new StreamGobbler(session.getStdout()), errStream);
} catch (IOException e) {
throw new IORuntimeException(e);
} finally {
close(session);
}
return result;
}
/**
* 执行Shell命令
* <p>
* 此方法单次发送一个命令到服务端自动读取环境变量执行结束后自动关闭Session不会产生阻塞
* </p>
*
* @param session Session会话
* @param cmd 命令
* @param charset 发送和读取内容的编码
* @param errStream 错误信息输出到的位置
*/
public static String execByShell(Session session, String cmd, Charset charset, OutputStream errStream) {
final String result;
try {
session.requestDumbPTY();
IoUtil.write(session.getStdin(), charset, true, cmd);
result = IoUtil.read(new StreamGobbler(session.getStdout()), charset);
if(null != errStream){
// 错误输出
IoUtil.copy(new StreamGobbler(session.getStdout()), errStream);
}
} catch (IOException e) {
throw new IORuntimeException(e);
} finally {
close(session);
}
return result;
}
/**
* 关闭会话
*
* @param session 会话通道
*/
public static void close(Session session) {
if (session != null) {
session.close();
}
}
}

View File

@ -417,7 +417,7 @@ public class JschUtil {
* @param cmd 命令
* @param charset 发送和读取内容的编码
* @param errStream 错误信息输出到的位置
* @return {@link ChannelExec}
* @return 执行结果内容
* @since 4.3.1
*/
public static String exec(Session session, String cmd, Charset charset, OutputStream errStream) {
@ -432,7 +432,7 @@ public class JschUtil {
try {
channel.connect();
in = channel.getInputStream();
return IoUtil.read(in, CharsetUtil.CHARSET_UTF_8);
return IoUtil.read(in, charset);
} catch (IOException e) {
throw new IORuntimeException(e);
} catch (JSchException e) {
@ -461,7 +461,6 @@ public class JschUtil {
shell.setPty(true);
OutputStream out = null;
InputStream in = null;
final StringBuilder result = StrUtil.builder();
try {
out = shell.getOutputStream();
in = shell.getInputStream();
@ -469,9 +468,7 @@ public class JschUtil {
out.write(StrUtil.bytes(cmd, charset));
out.flush();
while (in.available() > 0) {
result.append(IoUtil.read(in, charset));
}
return IoUtil.read(in, charset);
} catch (IOException e) {
throw new IORuntimeException(e);
} finally {
@ -479,7 +476,6 @@ public class JschUtil {
IoUtil.close(in);
close(shell);
}
return result.toString();
}
/**

View File

@ -10,9 +10,9 @@ import org.springframework.test.context.junit4.SpringRunner;
* @author sidian
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = EnableSprintUtilTest.class)
@SpringBootTest(classes = EnableSpringUtilTest.class)
@EnableSpringUtil
public class EnableSprintUtilTest {
public class EnableSpringUtilTest {
@Test
public void test() {

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</parent>
<artifactId>hutool-http</artifactId>

View File

@ -1008,6 +1008,17 @@ public class HttpRequest extends HttpBase<HttpRequest> {
return proxyAuth(HttpUtil.buildBasicAuth(username, password, charset));
}
/**
* 令牌验证生成的头类似于"Authorization: Bearer XXXXX"一般用于JWT
*
* @param token 令牌内容
* @return HttpRequest
* @since 5.5.3
*/
public HttpRequest bearerAuth(String token) {
return auth("Bearer " + token);
}
/**
* 验证简单插入Authorization头
*
@ -1094,9 +1105,9 @@ public class HttpRequest extends HttpBase<HttpRequest> {
}
/**
* 调用转发如果需要转发返回转发结果否则返回<code>null</code>
* 调用转发如果需要转发返回转发结果否则返回{@code null}
*
* @return {@link HttpResponse}无转发返回 <code>null</code>
* @return {@link HttpResponse}无转发返回 {@code null}
*/
private HttpResponse sendRedirectIfPossible() {
if (this.maxRedirectCount < 1) {

View File

@ -404,7 +404,7 @@ public class HttpUtil {
*/
private static HttpResponse requestDownloadFile(String url, File destFile, int timeout) {
Assert.notBlank(url, "[url] is blank !");
Assert.notNull(url, "[destFile] is null !");
Assert.notNull(destFile, "[destFile] is null !");
final HttpResponse response = HttpRequest.get(url).timeout(timeout).executeAsync();
if (response.isOk()) {

View File

@ -1,4 +1,4 @@
package cn.hutool.http.test;
package cn.hutool.http;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;

View File

@ -1,4 +1,4 @@
package cn.hutool.http.test;
package cn.hutool.http;
import org.junit.Assert;
import org.junit.Test;

View File

@ -1,17 +1,16 @@
package cn.hutool.http.test;
package cn.hutool.http;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.ssl.SSLSocketFactoryBuilder;
import cn.hutool.json.JSONUtil;
import org.junit.Ignore;
import org.junit.Test;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -144,4 +143,25 @@ public class HttpRequestTest {
Console.log(execute.body());
}
@Test
public void getByProxy(){
System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
// 用户名密码, 若已添加白名单则不需要添加
final String ProxyUser = "t10757311156848";
final String ProxyPass = "ikm5uu44";
Authenticator.setDefault(new Authenticator() {
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(ProxyUser, ProxyPass.toCharArray());
}
});
final HttpResponse res = HttpRequest.get("https://httpbin.org/get")
.basicAuth(ProxyUser, ProxyPass)
.setHttpProxy("tps193.kdlapi.com", 15818).execute();
Console.log(res.body());
}
}

View File

@ -1,4 +1,4 @@
package cn.hutool.http.test;
package cn.hutool.http;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Console;

View File

@ -1,4 +1,4 @@
package cn.hutool.http.test;
package cn.hutool.http;
import cn.hutool.core.lang.Console;
import cn.hutool.core.thread.ThreadUtil;

View File

@ -1,4 +1,4 @@
package cn.hutool.http.test;
package cn.hutool.http;
import cn.hutool.core.lang.Console;
import cn.hutool.http.Header;

View File

@ -1,20 +1,20 @@
package cn.hutool.http.test;
package cn.hutool.http;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
/**
* 文件上传单元测试
* @author looly
*
* @author looly
*/
public class UploadTest {
@ -48,4 +48,24 @@ public class UploadTest {
String result = HttpUtil.post("http://wthrcdn.etouch.cn/weather_mini", paramMap);
System.out.println(result);
}
@Test
@Ignore
public void uploadTest() {
//客户端
String url = "http://localhost:8888/file";
Path file = Paths.get("D:\\testBigData_upload.xlsx");
Map<String, String> headers = new HashMap<>(16);
headers.put("md5", "aaaaaaaa");
Map<String, Object> params = new HashMap<>(16);
params.put("fileName", file.toFile().getName());
params.put("file", file.toFile());
HttpRequest httpRequest = HttpRequest.post(url)
.setChunkedStreamingMode(1024 * 1024)
.headerMap(headers, false)
.form(params);
HttpResponse httpResponse = httpRequest.execute();
Console.log(httpResponse);
}
}

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.5.2</version>
<version>5.5.3</version>
</parent>
<artifactId>hutool-json</artifactId>

Some files were not shown because too many files have changed in this diff Show More