mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
Prepare release
This commit is contained in:
commit
987c3fab4f
39
CHANGELOG.md
39
CHANGELOG.md
@ -3,6 +3,43 @@
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.5.3 (2020-12-11)
|
||||
|
||||
### 新特性
|
||||
* 【core 】 IdcardUtil增加行政区划83(issue#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 】 增加FoundWord(pr#1290@Github)
|
||||
* 【core 】 增加Segment(pr#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改为unlockRead(issue#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)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
@ -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.
|
||||
|
@ -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平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
|
@ -1 +1 @@
|
||||
5.5.2
|
||||
5.5.3
|
||||
|
@ -1 +1 @@
|
||||
var version = '5.5.2'
|
||||
var version = '5.5.3'
|
@ -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>
|
||||
|
@ -45,6 +45,7 @@ public class Hutool {
|
||||
/**
|
||||
* 显示Hutool所有的工具类
|
||||
*
|
||||
* @return 工具类名集合
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public static Set<Class<?>> getAllUtils() {
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
// 过期
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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]);
|
||||
|
@ -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
|
||||
|
@ -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 相差年数
|
||||
|
@ -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 结束日期
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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},则调用之
|
||||
|
227
hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java
Normal file
227
hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java
Normal 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) {
|
||||
// 静默关闭
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
41
hutool-core/src/main/java/cn/hutool/core/lang/Segment.java
Normal file
41
hutool-core/src/main/java/cn/hutool/core/lang/Segment.java
Normal 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());
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
41
hutool-core/src/main/java/cn/hutool/core/net/PassAuth.java
Normal file
41
hutool-core/src/main/java/cn/hutool/core/net/PassAuth.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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 = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
|
||||
* </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();
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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 上传文件的大小,> 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;
|
||||
|
@ -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 是否为文件
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
4231
hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
Normal file
4231
hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据总数计算总页数
|
||||
*
|
||||
|
@ -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
@ -65,6 +65,36 @@ import java.util.Map;
|
||||
*/
|
||||
public class XmlUtil {
|
||||
|
||||
/**
|
||||
* 字符串常量:XML 空格转义 {@code " " -> " "}
|
||||
*/
|
||||
public static final String NBSP = " ";
|
||||
|
||||
/**
|
||||
* 字符串常量:XML And 符转义 {@code "&" -> "&"}
|
||||
*/
|
||||
public static final String AMP = "&";
|
||||
|
||||
/**
|
||||
* 字符串常量:XML 双引号转义 {@code """ -> "\""}
|
||||
*/
|
||||
public static final String QUOTE = """;
|
||||
|
||||
/**
|
||||
* 字符串常量:XML 单引号转义 {@code "&apos" -> "'"}
|
||||
*/
|
||||
public static final String APOS = "'";
|
||||
|
||||
/**
|
||||
* 字符串常量:XML 小于号转义 {@code "<" -> "<"}
|
||||
*/
|
||||
public static final String LT = "<";
|
||||
|
||||
/**
|
||||
* 字符串常量:XML 大于号转义 {@code ">" -> ">"}
|
||||
*/
|
||||
public static final String GT = ">";
|
||||
|
||||
/**
|
||||
* 在XML中无效的字符 正则
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
@ -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", " > 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", " > 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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
299
hutool-db/src/main/java/cn/hutool/db/DialectRunner.java
Normal file
299
hutool-db/src/main/java/cn/hutool/db/DialectRunner.java
Normal 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
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -17,8 +17,8 @@ public class H2Dialect extends AnsiSqlDialect {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DialectName dialectName() {
|
||||
return DialectName.H2;
|
||||
public String dialectName() {
|
||||
return DialectName.H2.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -23,7 +23,7 @@ public class MysqlDialect extends AnsiSqlDialect{
|
||||
}
|
||||
|
||||
@Override
|
||||
public DialectName dialectName() {
|
||||
return DialectName.MYSQL;
|
||||
public String dialectName() {
|
||||
return DialectName.MYSQL.toString();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ public class PostgresqlDialect extends AnsiSqlDialect{
|
||||
}
|
||||
|
||||
@Override
|
||||
public DialectName dialectName() {
|
||||
return DialectName.POSTGREESQL;
|
||||
public String dialectName() {
|
||||
return DialectName.POSTGREESQL.name();
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ public class SqlServer2012Dialect extends AnsiSqlDialect {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DialectName dialectName() {
|
||||
return DialectName.SQLSERVER2012;
|
||||
public String dialectName() {
|
||||
return DialectName.SQLSERVER2012.name();
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ public class Sqlite3Dialect extends AnsiSqlDialect{
|
||||
}
|
||||
|
||||
@Override
|
||||
public DialectName dialectName() {
|
||||
return DialectName.SQLITE3;
|
||||
public String dialectName() {
|
||||
return DialectName.SQLITE3.name();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import java.sql.SQLException;
|
||||
* @author Luxiaolei
|
||||
*
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RsHandler<T> extends Serializable{
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
/**
|
||||
* 用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQL DDL(数据定义语言)语句,例如 CREATE TABLE 和 DROP TABLE。<br>
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
61
hutool-dfa/src/main/java/cn/hutool/dfa/FoundWord.java
Normal file
61
hutool-dfa/src/main/java/cn/hutool/dfa/FoundWord.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -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
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
||||
|
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Spring相关工具封装
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.extra.spring;
|
130
hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java
Normal file
130
hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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() {
|
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -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()) {
|
||||
|
@ -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;
|
@ -1,4 +1,4 @@
|
||||
package cn.hutool.http.test;
|
||||
package cn.hutool.http;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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;
|
@ -1,4 +1,4 @@
|
||||
package cn.hutool.http.test;
|
||||
package cn.hutool.http;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.http.Header;
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
Loading…
x
Reference in New Issue
Block a user