mirror of
https://gitee.com/dromara/hutool.git
synced 2025-04-05 17:37:59 +08:00
Merge remote-tracking branch 'origin/v5-dev' into v5-dev
This commit is contained in:
commit
e1de291063
@ -2,7 +2,7 @@
|
||||
# 🚀Changelog
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.7.23 (2022-03-09)
|
||||
# 5.7.23 (2022-03-15)
|
||||
|
||||
### 🐣新特性
|
||||
* 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee)
|
||||
@ -11,11 +11,18 @@
|
||||
* 【crypto 】 增加XXTEA实现(issue#I4WH2X@Gitee)
|
||||
* 【core 】 增加Table实现(issue#2179@Github)
|
||||
* 【core 】 增加UniqueKeySet(issue#I4WUWR@Gitee)
|
||||
* 【core 】 阿拉伯数字转换成中文对发票票面金额转换的扩展(pr#570@Gitee)
|
||||
* 【core 】 ArrayUtil增加replace方法(pr#570@Gitee)
|
||||
* 【core 】 CsvReadConfig增加自定义标题行行号(issue#2180@Github)
|
||||
* 【db 】 增加MongoDB4.x支持(pr#568@Gitee)
|
||||
*
|
||||
### 🐞Bug修复
|
||||
* 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee)
|
||||
* 【core 】 修复NumberConverter对数字转换的问题(issue#I4WPF4@Gitee)
|
||||
* 【core 】 修复ReflectUtil.getMethods获取接口方法问题(issue#I4WUWR@Gitee)
|
||||
* 【core 】 修复NamingCase中大写转换问题(pr#572@Gitee)
|
||||
* 【http 】 修复GET重定向时,携带参数问题(issue#2189@Github)
|
||||
* 【core 】 修复FileUtil、FileCopier相对路径获取父路径错误问题(pr#2188@Github)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.7.22 (2022-03-01)
|
||||
|
@ -111,6 +111,8 @@ Each module can be introduced individually, or all modules can be introduced by
|
||||
|
||||
[📘Chinese documentation](https://www.hutool.cn/docs/)
|
||||
|
||||
[📘Chinese back-up documentation](https://plus.hutool.cn/docs/#/)
|
||||
|
||||
[📙API](https://apidoc.gitee.com/dromara/hutool/)
|
||||
|
||||
[🎬Video](https://www.bilibili.com/video/BV1bQ4y1M7d9?p=2)
|
||||
|
@ -107,6 +107,8 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
|
||||
|
||||
[📘中文文档](https://www.hutool.cn/docs/)
|
||||
|
||||
[📘中文备用文档](https://plus.hutool.cn/docs/#/)
|
||||
|
||||
[📙参考API](https://apidoc.gitee.com/dromara/hutool/)
|
||||
|
||||
[🎬视频介绍](https://www.bilibili.com/video/BV1bQ4y1M7d9?p=2)
|
||||
|
@ -40,7 +40,6 @@ public class NumberChineseFormatter {
|
||||
new ChineseUnit('亿', 1_0000_0000, true),
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 阿拉伯数字转换成中文,小数点后四舍五入保留两位. 使用于整数、小数的转换.
|
||||
*
|
||||
@ -53,15 +52,26 @@ public class NumberChineseFormatter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 阿拉伯数字转换成中文,小数点后四舍五入保留两位. 使用于整数、小数的转换.
|
||||
* 阿拉伯数字转换成中文.
|
||||
*
|
||||
* <p>主要是对发票票面金额转换的扩展
|
||||
* <p>如:-12.32
|
||||
* <p>发票票面转换为:(负数)壹拾贰圆叁角贰分
|
||||
* <p>而非:负壹拾贰元叁角贰分
|
||||
* <p>共两点不同:1、(负数) 而非 负;2、圆 而非 元
|
||||
* 2022/3/9
|
||||
*
|
||||
* @param amount 数字
|
||||
* @param isUseTraditional 是否使用繁体
|
||||
* @param isMoneyMode 是否为金额模式
|
||||
* @return 中文
|
||||
* @param isMoneyMode 是否金额模式
|
||||
* @param negativeName 负号转换名称 如:负、(负数)
|
||||
* @param unitName 单位名称 如:元、圆
|
||||
* @return java.lang.String
|
||||
* @author machuanpeng
|
||||
* @since 5.7.23
|
||||
*/
|
||||
public static String format(double amount, boolean isUseTraditional, boolean isMoneyMode) {
|
||||
if(0 == amount){
|
||||
public static String format(double amount, boolean isUseTraditional, boolean isMoneyMode, String negativeName, String unitName) {
|
||||
if (0 == amount) {
|
||||
return "零";
|
||||
}
|
||||
Assert.checkBetween(amount, -99_9999_9999_9999.99, 99_9999_9999_9999.99,
|
||||
@ -71,7 +81,7 @@ public class NumberChineseFormatter {
|
||||
|
||||
// 负数
|
||||
if (amount < 0) {
|
||||
chineseStr.append("负");
|
||||
chineseStr.append(StrUtil.isNullOrUndefined(negativeName) ? "负" : negativeName);
|
||||
amount = -amount;
|
||||
}
|
||||
|
||||
@ -82,44 +92,44 @@ public class NumberChineseFormatter {
|
||||
yuan = yuan / 10;
|
||||
|
||||
// 元
|
||||
if(false == isMoneyMode || 0 != yuan){
|
||||
if (false == isMoneyMode || 0 != yuan) {
|
||||
// 金额模式下,无需“零元”
|
||||
chineseStr.append(longToChinese(yuan, isUseTraditional));
|
||||
if(isMoneyMode){
|
||||
chineseStr.append("元");
|
||||
if (isMoneyMode) {
|
||||
chineseStr.append(StrUtil.isNullOrUndefined(unitName) ? "元" : unitName);
|
||||
}
|
||||
}
|
||||
|
||||
if(0 == jiao && 0 == fen){
|
||||
if (0 == jiao && 0 == fen) {
|
||||
//无小数部分的金额结尾
|
||||
if(isMoneyMode){
|
||||
if (isMoneyMode) {
|
||||
chineseStr.append("整");
|
||||
}
|
||||
return chineseStr.toString();
|
||||
}
|
||||
|
||||
// 小数部分
|
||||
if(false == isMoneyMode){
|
||||
if (false == isMoneyMode) {
|
||||
chineseStr.append("点");
|
||||
}
|
||||
|
||||
// 角
|
||||
if(0 == yuan && 0 == jiao){
|
||||
if (0 == yuan && 0 == jiao) {
|
||||
// 元和角都为0时,只有非金额模式下补“零”
|
||||
if(false == isMoneyMode){
|
||||
if (false == isMoneyMode) {
|
||||
chineseStr.append("零");
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
chineseStr.append(numberToChinese(jiao, isUseTraditional));
|
||||
if(isMoneyMode && 0 != jiao){
|
||||
if (isMoneyMode && 0 != jiao) {
|
||||
chineseStr.append("角");
|
||||
}
|
||||
}
|
||||
|
||||
// 分
|
||||
if(0 != fen){
|
||||
if (0 != fen) {
|
||||
chineseStr.append(numberToChinese(fen, isUseTraditional));
|
||||
if(isMoneyMode){
|
||||
if (isMoneyMode) {
|
||||
chineseStr.append("分");
|
||||
}
|
||||
}
|
||||
@ -127,16 +137,28 @@ public class NumberChineseFormatter {
|
||||
return chineseStr.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 阿拉伯数字转换成中文,小数点后四舍五入保留两位. 使用于整数、小数的转换.
|
||||
*
|
||||
* @param amount 数字
|
||||
* @param isUseTraditional 是否使用繁体
|
||||
* @param isMoneyMode 是否为金额模式
|
||||
* @return 中文
|
||||
*/
|
||||
public static String format(double amount, boolean isUseTraditional, boolean isMoneyMode) {
|
||||
return format(amount, isUseTraditional, isMoneyMode, "负", "元");
|
||||
}
|
||||
|
||||
/**
|
||||
* 阿拉伯数字(支持正负整数)转换成中文
|
||||
*
|
||||
* @param amount 数字
|
||||
* @param amount 数字
|
||||
* @param isUseTraditional 是否使用繁体
|
||||
* @return 中文
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public static String format(long amount, boolean isUseTraditional){
|
||||
if(0 == amount){
|
||||
public static String format(long amount, boolean isUseTraditional) {
|
||||
if (0 == amount) {
|
||||
return "零";
|
||||
}
|
||||
Assert.checkBetween(amount, -99_9999_9999_9999.99, 99_9999_9999_9999.99,
|
||||
@ -179,16 +201,16 @@ public class NumberChineseFormatter {
|
||||
* 格式化-999~999之间的数字<br>
|
||||
* 这个方法显示10~19以下的数字时使用"十一"而非"一十一"。
|
||||
*
|
||||
* @param amount 数字
|
||||
* @param amount 数字
|
||||
* @param isUseTraditional 是否使用繁体
|
||||
* @return 中文
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public static String formatThousand(int amount, boolean isUseTraditional){
|
||||
public static String formatThousand(int amount, boolean isUseTraditional) {
|
||||
Assert.checkBetween(amount, -999, 999, "Number support only: (-999 ~ 999)!");
|
||||
|
||||
final String chinese = thousandToChinese(amount, isUseTraditional);
|
||||
if(amount < 20 && amount >= 10){
|
||||
if (amount < 20 && amount >= 10) {
|
||||
// "十一"而非"一十一"
|
||||
return chinese.substring(1);
|
||||
}
|
||||
@ -218,7 +240,7 @@ public class NumberChineseFormatter {
|
||||
* @return 中文
|
||||
*/
|
||||
private static String longToChinese(long amount, boolean isUseTraditional) {
|
||||
if(0 == amount){
|
||||
if (0 == amount) {
|
||||
return "零";
|
||||
}
|
||||
|
||||
@ -235,11 +257,11 @@ public class NumberChineseFormatter {
|
||||
|
||||
// 千
|
||||
partValue = parts[0];
|
||||
if(partValue > 0){
|
||||
if (partValue > 0) {
|
||||
partChinese = thousandToChinese(partValue, isUseTraditional);
|
||||
chineseStr.insert(0, partChinese);
|
||||
|
||||
if(partValue < 1000){
|
||||
if (partValue < 1000) {
|
||||
// 和万位之间空0,则补零,如一万零三百
|
||||
addPreZero(chineseStr);
|
||||
}
|
||||
@ -247,26 +269,26 @@ public class NumberChineseFormatter {
|
||||
|
||||
// 万
|
||||
partValue = parts[1];
|
||||
if(partValue > 0){
|
||||
if((partValue % 10 == 0 && parts[0] > 0)){
|
||||
if (partValue > 0) {
|
||||
if ((partValue % 10 == 0 && parts[0] > 0)) {
|
||||
// 如果"万"的个位是0,则补零,如十万零八千
|
||||
addPreZero(chineseStr);
|
||||
}
|
||||
partChinese = thousandToChinese(partValue, isUseTraditional);
|
||||
chineseStr.insert(0, partChinese + "万");
|
||||
|
||||
if(partValue < 1000){
|
||||
if (partValue < 1000) {
|
||||
// 和亿位之间空0,则补零,如一亿零三百万
|
||||
addPreZero(chineseStr);
|
||||
}
|
||||
} else{
|
||||
} else {
|
||||
addPreZero(chineseStr);
|
||||
}
|
||||
|
||||
// 亿
|
||||
partValue = parts[2];
|
||||
if(partValue > 0){
|
||||
if((partValue % 10 == 0 && parts[1] > 0)){
|
||||
if (partValue > 0) {
|
||||
if ((partValue % 10 == 0 && parts[1] > 0)) {
|
||||
// 如果"万"的个位是0,则补零,如十万零八千
|
||||
addPreZero(chineseStr);
|
||||
}
|
||||
@ -274,25 +296,25 @@ public class NumberChineseFormatter {
|
||||
partChinese = thousandToChinese(partValue, isUseTraditional);
|
||||
chineseStr.insert(0, partChinese + "亿");
|
||||
|
||||
if(partValue < 1000){
|
||||
if (partValue < 1000) {
|
||||
// 和万亿位之间空0,则补零,如一万亿零三百亿
|
||||
addPreZero(chineseStr);
|
||||
}
|
||||
} else{
|
||||
} else {
|
||||
addPreZero(chineseStr);
|
||||
}
|
||||
|
||||
// 万亿
|
||||
partValue = parts[3];
|
||||
if(partValue > 0){
|
||||
if(parts[2] == 0){
|
||||
if (partValue > 0) {
|
||||
if (parts[2] == 0) {
|
||||
chineseStr.insert(0, "亿");
|
||||
}
|
||||
partChinese = thousandToChinese(partValue, isUseTraditional);
|
||||
chineseStr.insert(0, partChinese + "万");
|
||||
}
|
||||
|
||||
if(StrUtil.isNotEmpty(chineseStr) && '零' == chineseStr.charAt(0)){
|
||||
if (StrUtil.isNotEmpty(chineseStr) && '零' == chineseStr.charAt(0)) {
|
||||
return chineseStr.substring(1);
|
||||
}
|
||||
|
||||
@ -386,7 +408,7 @@ public class NumberChineseFormatter {
|
||||
} else {
|
||||
// 非节单位,和单位前的单数字组合为值
|
||||
int unitNumber = number;
|
||||
if(0 == number && 0 == i){
|
||||
if (0 == number && 0 == i) {
|
||||
// issue#1726,对于单位开头的数组,默认赋予1
|
||||
// 十二 -> 一十二
|
||||
// 百二 -> 一百二
|
||||
@ -502,12 +524,12 @@ public class NumberChineseFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
private static void addPreZero(StringBuilder chineseStr){
|
||||
if(StrUtil.isEmpty(chineseStr)){
|
||||
private static void addPreZero(StringBuilder chineseStr) {
|
||||
if (StrUtil.isEmpty(chineseStr)) {
|
||||
return;
|
||||
}
|
||||
final char c = chineseStr.charAt(0);
|
||||
if('零' != c){
|
||||
if ('零' != c) {
|
||||
chineseStr.insert(0, '零');
|
||||
}
|
||||
}
|
||||
|
@ -700,7 +700,7 @@ public class FileUtil extends PathUtil {
|
||||
if (null == file) {
|
||||
return null;
|
||||
}
|
||||
return mkdir(file.getParentFile());
|
||||
return mkdir(getParent(file, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -267,8 +267,7 @@ public class FileCopier extends SrcToDestCopier<File, FileCopier>{
|
||||
}
|
||||
}else {
|
||||
//路径不存在则创建父目录
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
dest.getParentFile().mkdirs();
|
||||
FileUtil.mkParentDirs(dest);
|
||||
}
|
||||
|
||||
final ArrayList<CopyOption> optionList = new ArrayList<>(2);
|
||||
|
@ -97,7 +97,7 @@ public class NamingCase {
|
||||
// 后一个为大写,按照专有名词对待,如aBC -> a_BC
|
||||
} else {
|
||||
//前一个为大写
|
||||
if (null == nextChar || Character.isLowerCase(nextChar)) {
|
||||
if (null != nextChar && Character.isLowerCase(nextChar)) {
|
||||
// 普通首字母大写,如ABcc -> A_bcc
|
||||
sb.append(symbol);
|
||||
c = Character.toLowerCase(c);
|
||||
@ -157,7 +157,7 @@ public class NamingCase {
|
||||
* 将连接符方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。
|
||||
*
|
||||
* @param name 转换前的自定义方式命名的字符串
|
||||
* @param symbol 连接符
|
||||
* @param symbol 原字符串中的连接符连接符
|
||||
* @return 转换后的驼峰式命名的字符串
|
||||
* @since 5.7.17
|
||||
*/
|
||||
|
@ -177,7 +177,7 @@ public class CsvBaseReader implements Serializable {
|
||||
final CsvParser csvParser = parse(reader);
|
||||
final List<CsvRow> rows = new ArrayList<>();
|
||||
read(csvParser, rows::add);
|
||||
final List<String> header = config.containsHeader ? csvParser.getHeader() : null;
|
||||
final List<String> header = config.headerLineNo > -1 ? csvParser.getHeader() : null;
|
||||
|
||||
return new CsvData(header, rows);
|
||||
}
|
||||
|
@ -84,13 +84,13 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取头部字段列表,如果containsHeader设置为false则抛出异常
|
||||
* 获取头部字段列表,如果headerLineNo < 0,抛出异常
|
||||
*
|
||||
* @return 头部列表
|
||||
* @throws IllegalStateException 如果不解析头部或者没有调用nextRow()方法
|
||||
*/
|
||||
public List<String> getHeader() {
|
||||
if (false == config.containsHeader) {
|
||||
if (config.headerLineNo < 0) {
|
||||
throw new IllegalStateException("No header available - header parsing is disabled");
|
||||
}
|
||||
if (lineNo < config.beginLineNo) {
|
||||
@ -152,7 +152,7 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
|
||||
}
|
||||
|
||||
//初始化标题
|
||||
if (config.containsHeader && null == header) {
|
||||
if (lineNo == config.headerLineNo && null == header) {
|
||||
initHeader(currentFields);
|
||||
// 作为标题行后,此行跳过,下一行做为第一行
|
||||
continue;
|
||||
|
@ -11,8 +11,8 @@ import java.io.Serializable;
|
||||
public class CsvReadConfig extends CsvConfig<CsvReadConfig> implements Serializable {
|
||||
private static final long serialVersionUID = 5396453565371560052L;
|
||||
|
||||
/** 是否首行做为标题行,默认false */
|
||||
protected boolean containsHeader;
|
||||
/** 指定标题行号,-1表示无标题行 */
|
||||
protected long headerLineNo = -1;
|
||||
/** 是否跳过空白行,默认true */
|
||||
protected boolean skipEmptyRows = true;
|
||||
/** 每行字段个数不同时是否抛出异常,默认false */
|
||||
@ -34,13 +34,26 @@ public class CsvReadConfig extends CsvConfig<CsvReadConfig> implements Serializa
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否首行做为标题行,默认false
|
||||
* 设置是否首行做为标题行,默认false<br>
|
||||
* 当设置为{@code true}时,默认标题行号是{@link #beginLineNo},{@code false}为-1,表示无行号
|
||||
*
|
||||
* @param containsHeader 是否首行做为标题行,默认false
|
||||
* @return this
|
||||
* @see #setHeaderLineNo(long)
|
||||
*/
|
||||
public CsvReadConfig setContainsHeader(boolean containsHeader) {
|
||||
this.containsHeader = containsHeader;
|
||||
return setHeaderLineNo(containsHeader ? beginLineNo : -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置标题行行号,默认-1,表示无标题行<br>
|
||||
*
|
||||
* @param headerLineNo 标题行行号,-1表示无标题行
|
||||
* @return this
|
||||
* @since 5.7.23
|
||||
*/
|
||||
public CsvReadConfig setHeaderLineNo(long headerLineNo) {
|
||||
this.headerLineNo = headerLineNo;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -368,6 +368,48 @@ public class ArrayUtil extends PrimitiveArrayUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将新元素插入到到已有数组中的某个位置<br>
|
||||
* 添加新元素会生成一个新数组或原有数组<br>
|
||||
* 如果插入位置为为负数,那么生成一个由插入元素顺序加已有数组顺序的新数组
|
||||
*
|
||||
* @param <T> 数组元素类型
|
||||
* @param buffer 已有数组
|
||||
* @param index 位置,大于长度追加,否则替换,<0表示从头部追加
|
||||
* @param values 新值
|
||||
* @return 新数组或原有数组
|
||||
* @since 5.7.23
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static <T> T[] replace(T[] buffer, int index, T... values) {
|
||||
if(isEmpty(values)){
|
||||
return buffer;
|
||||
}
|
||||
if(isEmpty(buffer)){
|
||||
return values;
|
||||
}
|
||||
if (index < 0) {
|
||||
// 从头部追加
|
||||
return insert(buffer, 0, values);
|
||||
}
|
||||
if (index >= buffer.length) {
|
||||
// 超出长度,尾部追加
|
||||
return append(buffer, values);
|
||||
}
|
||||
|
||||
if (buffer.length >= values.length + index) {
|
||||
System.arraycopy(values, 0, buffer, index, values.length);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// 替换长度大于原数组长度,新建数组
|
||||
int newArrayLength = index + values.length;
|
||||
final T[] result = newArray(buffer.getClass().getComponentType(), newArrayLength);
|
||||
System.arraycopy(buffer, 0, result, 0, index);
|
||||
System.arraycopy(values, 0, result, index, values.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将新元素插入到到已有数组中的某个位置<br>
|
||||
* 添加新元素会生成一个新的数组,不影响原数组<br>
|
||||
@ -1540,7 +1582,7 @@ public class ArrayUtil extends PrimitiveArrayUtil {
|
||||
* @since 4.5.18
|
||||
*/
|
||||
public static boolean isAllEmpty(Object... args) {
|
||||
for (Object obj: args) {
|
||||
for (Object obj : args) {
|
||||
if (false == ObjectUtil.isEmpty(obj)) {
|
||||
return false;
|
||||
}
|
||||
@ -1758,10 +1800,10 @@ public class ArrayUtil extends PrimitiveArrayUtil {
|
||||
/**
|
||||
* 查找最后一个子数组的开始位置
|
||||
*
|
||||
* @param array 数组
|
||||
* @param array 数组
|
||||
* @param endInclude 查找结束的位置(包含)
|
||||
* @param subArray 子数组
|
||||
* @param <T> 数组元素类型
|
||||
* @param subArray 子数组
|
||||
* @param <T> 数组元素类型
|
||||
* @return 最后一个子数组的开始位置,即子数字第一个元素在数组中的位置
|
||||
* @since 5.4.8
|
||||
*/
|
||||
|
@ -1,45 +1,59 @@
|
||||
package cn.hutool.core.io;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.core.io.file.FileCopier;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 文件拷贝单元测试
|
||||
* @author Looly
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class FileCopierTest {
|
||||
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void dirCopyTest() {
|
||||
FileCopier copier = FileCopier.create("D:\\Java", "e:/eclipse/eclipse2.zip");
|
||||
copier.copy();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void dirCopyTest2() {
|
||||
//测试带.的文件夹复制
|
||||
FileCopier copier = FileCopier.create("D:\\workspace\\java\\.metadata", "D:\\workspace\\java\\.metadata\\temp");
|
||||
copier.copy();
|
||||
|
||||
|
||||
FileUtil.copy("D:\\workspace\\java\\looly\\hutool\\.git", "D:\\workspace\\java\\temp", true);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IORuntimeException.class)
|
||||
public void dirCopySubTest() {
|
||||
//测试父目录复制到子目录报错
|
||||
FileCopier copier = FileCopier.create("D:\\workspace\\java\\.metadata", "D:\\workspace\\java\\.metadata\\temp");
|
||||
copier.copy();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void copyFileToDirTest() {
|
||||
FileCopier copier = FileCopier.create("d:/GReen_Soft/XshellXftpPortable.zip", "c:/hp/");
|
||||
copier.copy();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void copyFileByRelativePath(){
|
||||
// https://github.com/dromara/hutool/pull/2188
|
||||
// 当复制的目标文件位置是相对路径的时候可以通过
|
||||
FileCopier copier = FileCopier.create(new File("pom.xml"),new File("aaa.txt"));
|
||||
copier.copy();
|
||||
final boolean delete = new File("aaa.txt").delete();
|
||||
Assert.assertTrue(delete);
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,44 @@
|
||||
package cn.hutool.core.text;
|
||||
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class NamingCaseTest {
|
||||
|
||||
@Test
|
||||
public void toUnderlineCaseTest(){
|
||||
// https://github.com/dromara/hutool/issues/2070
|
||||
public void toCamelCaseTest() {
|
||||
Dict.create()
|
||||
.set("customerNickV2", "customer_nick_v2")
|
||||
.forEach((key, value) -> Assert.assertEquals(value, NamingCase.toUnderlineCase(key)));
|
||||
.set("Table_Test_Of_day","tableTestOfDay")
|
||||
.set("TableTestOfDay","TableTestOfDay")
|
||||
.set("abc_1d","abc1d")
|
||||
.forEach((key, value) -> Assert.assertEquals(value, NamingCase.toCamelCase(key)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toUnderLineCaseTest2(){
|
||||
final String wPRunOZTime = NamingCase.toUnderlineCase("wPRunOZTime");
|
||||
Assert.assertEquals("w_P_run_OZ_time", wPRunOZTime);
|
||||
public void toCamelCaseFromDashedTest() {
|
||||
Dict.create()
|
||||
.set("Table-Test-Of-day","tableTestOfDay")
|
||||
.forEach((key, value) -> Assert.assertEquals(value, NamingCase.toCamelCase(key, CharUtil.DASHED)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toUnderLineCaseTest() {
|
||||
Dict.create()
|
||||
.set("Table_Test_Of_day", "table_test_of_day")
|
||||
.set("_Table_Test_Of_day_", "_table_test_of_day_")
|
||||
.set("_Table_Test_Of_DAY_", "_table_test_of_DAY_")
|
||||
.set("_TableTestOfDAYToday", "_table_test_of_DAY_today")
|
||||
.set("HelloWorld_test", "hello_world_test")
|
||||
.set("H2", "H2")
|
||||
.set("H#case", "H#case")
|
||||
.set("PNLabel", "PN_label")
|
||||
.set("wPRunOZTime", "w_P_run_OZ_time")
|
||||
// https://github.com/dromara/hutool/issues/2070
|
||||
.set("customerNickV2", "customer_nick_v2")
|
||||
// https://gitee.com/dromara/hutool/issues/I4X9TT
|
||||
.set("DEPT_NAME","DEPT_NAME")
|
||||
.forEach((key, value) -> Assert.assertEquals(value, NamingCase.toUnderlineCase(key)));
|
||||
}
|
||||
}
|
||||
|
@ -363,7 +363,7 @@ public class ArrayUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOfSubTest2(){
|
||||
public void indexOfSubTest2() {
|
||||
Integer[] a = {0x12, 0x56, 0x34, 0x56, 0x78, 0x9A};
|
||||
Integer[] b = {0x56, 0x78};
|
||||
int i = ArrayUtil.indexOfSub(a, b);
|
||||
@ -401,7 +401,7 @@ public class ArrayUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lastIndexOfSubTest2(){
|
||||
public void lastIndexOfSubTest2() {
|
||||
Integer[] a = {0x12, 0x56, 0x78, 0x56, 0x21, 0x9A};
|
||||
Integer[] b = {0x56, 0x78};
|
||||
int i = ArrayUtil.indexOfSub(a, b);
|
||||
@ -409,17 +409,17 @@ public class ArrayUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reverseTest(){
|
||||
int[] a = {1,2,3,4};
|
||||
public void reverseTest() {
|
||||
int[] a = {1, 2, 3, 4};
|
||||
final int[] reverse = ArrayUtil.reverse(a);
|
||||
Assert.assertArrayEquals(new int[]{4,3,2,1}, reverse);
|
||||
Assert.assertArrayEquals(new int[]{4, 3, 2, 1}, reverse);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reverseTest2s(){
|
||||
Object[] a = {"1",'2',"3",4};
|
||||
public void reverseTest2s() {
|
||||
Object[] a = {"1", '2', "3", 4};
|
||||
final Object[] reverse = ArrayUtil.reverse(a);
|
||||
Assert.assertArrayEquals(new Object[]{4,"3",'2',"1"}, reverse);
|
||||
Assert.assertArrayEquals(new Object[]{4, "3", '2', "1"}, reverse);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -461,9 +461,57 @@ public class ArrayUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTest(){
|
||||
public void getTest() {
|
||||
String[] a = {"a", "b", "c"};
|
||||
final Object o = ArrayUtil.get(a, -1);
|
||||
Assert.assertEquals("c", o);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceTest() {
|
||||
String[] a = {"1", "2", "3", "4"};
|
||||
String[] b = {"a", "b", "c"};
|
||||
|
||||
// 在小于0的位置,-1位置插入,返回b+a,新数组
|
||||
String[] result = ArrayUtil.replace(a, -1, b);
|
||||
Assert.assertArrayEquals(new String[]{"a", "b", "c", "1", "2", "3", "4"}, result);
|
||||
|
||||
// 在第0个位置开始替换,返回a
|
||||
result = ArrayUtil.replace(ArrayUtil.clone(a), 0, b);
|
||||
Assert.assertArrayEquals(new String[]{"a", "b", "c", "4"}, result);
|
||||
|
||||
// 在第1个位置替换,即"2"开始
|
||||
result = ArrayUtil.replace(ArrayUtil.clone(a), 1, b);
|
||||
Assert.assertArrayEquals(new String[]{"1", "a", "b", "c"}, result);
|
||||
|
||||
// 在第2个位置插入,即"3"之后
|
||||
result = ArrayUtil.replace(ArrayUtil.clone(a), 2, b);
|
||||
Assert.assertArrayEquals(new String[]{"1", "2", "a", "b", "c"}, result);
|
||||
|
||||
// 在第3个位置插入,即"4"之后
|
||||
result = ArrayUtil.replace(ArrayUtil.clone(a), 3, b);
|
||||
Assert.assertArrayEquals(new String[]{"1", "2", "3", "a", "b", "c"}, result);
|
||||
|
||||
// 在第4个位置插入,数组长度为4,在索引4出替换即两个数组相加
|
||||
result = ArrayUtil.replace(ArrayUtil.clone(a), 4, b);
|
||||
Assert.assertArrayEquals(new String[]{"1", "2", "3", "4", "a", "b", "c"}, result);
|
||||
|
||||
// 在大于3个位置插入,数组长度为4,即两个数组相加
|
||||
result = ArrayUtil.replace(ArrayUtil.clone(a), 5, b);
|
||||
Assert.assertArrayEquals(new String[]{"1", "2", "3", "4", "a", "b", "c"}, result);
|
||||
|
||||
String[] e = null;
|
||||
String[] f = {"a", "b", "c"};
|
||||
|
||||
// e为null 返回 f
|
||||
result = ArrayUtil.replace(e, -1, f);
|
||||
Assert.assertArrayEquals(f, result);
|
||||
|
||||
String[] g = {"a", "b", "c"};
|
||||
String[] h = null;
|
||||
|
||||
// h为null 返回 g
|
||||
result = ArrayUtil.replace(g, 0, h);
|
||||
Assert.assertArrayEquals(g, result);
|
||||
}
|
||||
}
|
||||
|
@ -391,40 +391,6 @@ public class StrUtilTest {
|
||||
Assert.assertEquals(text, str);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toCamelCaseTest() {
|
||||
String str = "Table_Test_Of_day";
|
||||
String result = StrUtil.toCamelCase(str);
|
||||
Assert.assertEquals("tableTestOfDay", result);
|
||||
|
||||
String str1 = "TableTestOfDay";
|
||||
String result1 = StrUtil.toCamelCase(str1);
|
||||
Assert.assertEquals("TableTestOfDay", result1);
|
||||
|
||||
String abc1d = StrUtil.toCamelCase("abc_1d");
|
||||
Assert.assertEquals("abc1d", abc1d);
|
||||
|
||||
|
||||
String str2 = "Table-Test-Of-day";
|
||||
String result2 = StrUtil.toCamelCase(str2, CharUtil.DASHED);
|
||||
System.out.println(result2);
|
||||
Assert.assertEquals("tableTestOfDay", result2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toUnderLineCaseTest() {
|
||||
Dict.create()
|
||||
.set("Table_Test_Of_day", "table_test_of_day")
|
||||
.set("_Table_Test_Of_day_", "_table_test_of_day_")
|
||||
.set("_Table_Test_Of_DAY_", "_table_test_of_DAY_")
|
||||
.set("_TableTestOfDAYToday", "_table_test_of_DAY_today")
|
||||
.set("HelloWorld_test", "hello_world_test")
|
||||
.set("H2", "H2")
|
||||
.set("H#case", "H#case")
|
||||
.set("PNLabel", "PN_label")
|
||||
.forEach((key, value) -> Assert.assertEquals(value, StrUtil.toUnderlineCase(key)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsAnyTest() {
|
||||
//字符
|
||||
|
@ -20,10 +20,9 @@
|
||||
<!-- versions -->
|
||||
<c3p0.version>0.9.5.5</c3p0.version>
|
||||
<dbcp2.version>2.9.0</dbcp2.version>
|
||||
<tomcat-jdbc.version>10.0.14</tomcat-jdbc.version>
|
||||
<tomcat-jdbc.version>10.0.16</tomcat-jdbc.version>
|
||||
<druid.version>1.2.8</druid.version>
|
||||
<hikariCP.version>2.4.13</hikariCP.version>
|
||||
<mongo.version>3.12.10</mongo.version>
|
||||
<mongo4.version>4.5.0</mongo4.version>
|
||||
<sqlite.version>3.36.0.3</sqlite.version>
|
||||
<!-- 此处固定2.5.x,支持到JDK8 -->
|
||||
@ -97,13 +96,6 @@
|
||||
<version>${dbcp2.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- MongoDB Java客户端 -->
|
||||
<dependency>
|
||||
<groupId>org.mongodb</groupId>
|
||||
<artifactId>mongo-java-driver</artifactId>
|
||||
<version>${mongo.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mongodb</groupId>
|
||||
<artifactId>mongodb-driver-sync</artifactId>
|
||||
|
@ -6,29 +6,35 @@ import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.db.DbRuntimeException;
|
||||
import cn.hutool.log.Log;
|
||||
import cn.hutool.setting.Setting;
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.MongoClientOptions;
|
||||
import com.mongodb.MongoClientOptions.Builder;
|
||||
import com.mongodb.MongoClientSettings;
|
||||
import com.mongodb.MongoCredential;
|
||||
import com.mongodb.ServerAddress;
|
||||
import com.mongodb.client.MongoClient;
|
||||
import com.mongodb.client.MongoClients;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.connection.ConnectionPoolSettings;
|
||||
import com.mongodb.connection.SocketSettings;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* MongoDB工具类
|
||||
*
|
||||
* @author xiaoleilu
|
||||
* MongoDB4工具类
|
||||
*
|
||||
* @author VampireAchao
|
||||
*/
|
||||
public class MongoDS implements Closeable {
|
||||
|
||||
private final static Log log = Log.get();
|
||||
|
||||
/** 默认配置文件 */
|
||||
/**
|
||||
* 默认配置文件
|
||||
*/
|
||||
public final static String MONGO_CONFIG_PATH = "config/mongo.setting";
|
||||
|
||||
// MongoDB配置文件
|
||||
@ -41,6 +47,7 @@ public class MongoDS implements Closeable {
|
||||
private MongoClient mongo;
|
||||
|
||||
// --------------------------------------------------------------------------- Constructor start
|
||||
|
||||
/**
|
||||
* 构造MongoDB数据源<br>
|
||||
* 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败!
|
||||
@ -58,8 +65,8 @@ public class MongoDS implements Closeable {
|
||||
* 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败!
|
||||
*
|
||||
* @param mongoSetting MongoDB的配置文件,如果是null则读取默认配置文件或者使用MongoDB默认客户端配置
|
||||
* @param host 主机(域名或者IP)
|
||||
* @param port 端口
|
||||
* @param host 主机(域名或者IP)
|
||||
* @param port 端口
|
||||
*/
|
||||
public MongoDS(Setting mongoSetting, String host, int port) {
|
||||
this.setting = mongoSetting;
|
||||
@ -86,7 +93,7 @@ public class MongoDS implements Closeable {
|
||||
* 官方文档: http://docs.mongodb.org/manual/administration/replica-sets/
|
||||
*
|
||||
* @param mongoSetting MongoDB的配置文件,必须有
|
||||
* @param groups 分组列表,当为null或空时使用无分组配置,一个分组使用单一模式,否则使用副本集模式
|
||||
* @param groups 分组列表,当为null或空时使用无分组配置,一个分组使用单一模式,否则使用副本集模式
|
||||
*/
|
||||
public MongoDS(Setting mongoSetting, String... groups) {
|
||||
if (mongoSetting == null) {
|
||||
@ -146,11 +153,13 @@ public class MongoDS implements Closeable {
|
||||
|
||||
final MongoCredential credentail = createCredentail(group);
|
||||
try {
|
||||
if (null == credentail) {
|
||||
mongo = new MongoClient(serverAddress, buildMongoClientOptions(group));
|
||||
} else {
|
||||
mongo = new MongoClient(serverAddress, credentail, buildMongoClientOptions(group));
|
||||
MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder()
|
||||
.applyToClusterSettings(b -> b.hosts(Collections.singletonList(serverAddress)));
|
||||
buildMongoClientSettings(clusterSettingsBuilder, group);
|
||||
if (null != credentail) {
|
||||
clusterSettingsBuilder.credential(credentail);
|
||||
}
|
||||
mongo = MongoClients.create(clusterSettingsBuilder.build());
|
||||
} catch (Exception e) {
|
||||
throw new DbRuntimeException(StrUtil.format("Init MongoDB pool with connection to [{}] error!", serverAddress), e);
|
||||
}
|
||||
@ -192,11 +201,13 @@ public class MongoDS implements Closeable {
|
||||
|
||||
final MongoCredential credentail = createCredentail(StrUtil.EMPTY);
|
||||
try {
|
||||
if (null == credentail) {
|
||||
mongo = new MongoClient(addrList, buildMongoClientOptions(StrUtil.EMPTY));
|
||||
} else {
|
||||
mongo = new MongoClient(addrList, credentail, buildMongoClientOptions(StrUtil.EMPTY));
|
||||
MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder()
|
||||
.applyToClusterSettings(b -> b.hosts(addrList));
|
||||
buildMongoClientSettings(clusterSettingsBuilder, StrUtil.EMPTY);
|
||||
if (null != credentail) {
|
||||
clusterSettingsBuilder.credential(credentail);
|
||||
}
|
||||
mongo = MongoClients.create(clusterSettingsBuilder.build());
|
||||
} catch (Exception e) {
|
||||
log.error(e, "Init MongoDB connection error!");
|
||||
return;
|
||||
@ -234,7 +245,7 @@ public class MongoDS implements Closeable {
|
||||
/**
|
||||
* 获得MongoDB中指定集合对象
|
||||
*
|
||||
* @param dbName 库名
|
||||
* @param dbName 库名
|
||||
* @param collectionName 集合名
|
||||
* @return DBCollection
|
||||
*/
|
||||
@ -248,6 +259,7 @@ public class MongoDS implements Closeable {
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
* 创建ServerAddress对象,会读取配置文件中的相关信息
|
||||
*
|
||||
@ -291,7 +303,7 @@ public class MongoDS implements Closeable {
|
||||
*/
|
||||
private MongoCredential createCredentail(String group) {
|
||||
final Setting setting = this.setting;
|
||||
if(null == setting) {
|
||||
if (null == setting) {
|
||||
return null;
|
||||
}
|
||||
final String user = setting.getStr("user", group, setting.getStr("user"));
|
||||
@ -316,23 +328,13 @@ public class MongoDS implements Closeable {
|
||||
return MongoCredential.createCredential(userName, database, password.toCharArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构件MongoDB连接选项<br>
|
||||
*
|
||||
* @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值
|
||||
* @return MongoClientOptions
|
||||
*/
|
||||
private MongoClientOptions buildMongoClientOptions(String group) {
|
||||
return buildMongoClientOptions(MongoClientOptions.builder(), group).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构件MongoDB连接选项<br>
|
||||
*
|
||||
* @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值
|
||||
* @return Builder
|
||||
*/
|
||||
private Builder buildMongoClientOptions(Builder builder, String group) {
|
||||
private MongoClientSettings.Builder buildMongoClientSettings(MongoClientSettings.Builder builder, String group) {
|
||||
if (setting == null) {
|
||||
return builder;
|
||||
}
|
||||
@ -348,8 +350,9 @@ public class MongoDS implements Closeable {
|
||||
if (StrUtil.isBlank(group) == false && connectionsPerHost == null) {
|
||||
connectionsPerHost = setting.getInt("connectionsPerHost");
|
||||
}
|
||||
ConnectionPoolSettings.Builder connectionPoolSettingsBuilder = ConnectionPoolSettings.builder();
|
||||
if (connectionsPerHost != null) {
|
||||
builder.connectionsPerHost(connectionsPerHost);
|
||||
connectionPoolSettingsBuilder.maxSize(connectionsPerHost);
|
||||
log.debug("MongoDB connectionsPerHost: {}", connectionsPerHost);
|
||||
}
|
||||
|
||||
@ -359,9 +362,10 @@ public class MongoDS implements Closeable {
|
||||
setting.getInt("connectTimeout");
|
||||
}
|
||||
if (connectTimeout != null) {
|
||||
builder.connectTimeout(connectTimeout);
|
||||
connectionPoolSettingsBuilder.maxWaitTime(connectTimeout, TimeUnit.MILLISECONDS);
|
||||
log.debug("MongoDB connectTimeout: {}", connectTimeout);
|
||||
}
|
||||
builder.applyToConnectionPoolSettings(b -> b.applySettings(connectionPoolSettingsBuilder.build()));
|
||||
|
||||
// 套接字超时时间;该值会被传递给Socket.setSoTimeout(int)。默以为0(无穷) --int
|
||||
Integer socketTimeout = setting.getInt(group + "socketTimeout");
|
||||
@ -369,7 +373,8 @@ public class MongoDS implements Closeable {
|
||||
setting.getInt("socketTimeout");
|
||||
}
|
||||
if (socketTimeout != null) {
|
||||
builder.socketTimeout(socketTimeout);
|
||||
SocketSettings socketSettings = SocketSettings.builder().connectTimeout(socketTimeout, TimeUnit.MILLISECONDS).build();
|
||||
builder.applyToSocketSettings(b -> b.applySettings(socketSettings));
|
||||
log.debug("MongoDB socketTimeout: {}", socketTimeout);
|
||||
}
|
||||
|
||||
@ -388,4 +393,5 @@ public class MongoDS implements Closeable {
|
||||
return this.setting;
|
||||
}
|
||||
// --------------------------------------------------------------------------- Private method end
|
||||
|
||||
}
|
||||
|
@ -1,404 +0,0 @@
|
||||
package cn.hutool.db.nosql.mongo;
|
||||
|
||||
import cn.hutool.core.exceptions.NotInitedException;
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.db.DbRuntimeException;
|
||||
import cn.hutool.log.Log;
|
||||
import cn.hutool.setting.Setting;
|
||||
import com.mongodb.MongoClientSettings;
|
||||
import com.mongodb.MongoCredential;
|
||||
import com.mongodb.MongoDriverInformation;
|
||||
import com.mongodb.ServerAddress;
|
||||
import com.mongodb.client.MongoClient;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.internal.MongoClientImpl;
|
||||
import com.mongodb.connection.ConnectionPoolSettings;
|
||||
import com.mongodb.connection.SocketSettings;
|
||||
import org.bson.Document;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* MongoDB4工具类
|
||||
*
|
||||
* @author VampireAchao
|
||||
*/
|
||||
public class MongoDS4 implements Closeable {
|
||||
|
||||
private final static Log log = Log.get();
|
||||
|
||||
/**
|
||||
* 默认配置文件
|
||||
*/
|
||||
public final static String MONGO_CONFIG_PATH = "config/mongo.setting";
|
||||
|
||||
// MongoDB配置文件
|
||||
private Setting setting;
|
||||
// MongoDB实例连接列表
|
||||
private String[] groups;
|
||||
// MongoDB单点连接信息
|
||||
private ServerAddress serverAddress;
|
||||
// MongoDB客户端对象
|
||||
private MongoClient mongo;
|
||||
|
||||
// --------------------------------------------------------------------------- Constructor start
|
||||
|
||||
/**
|
||||
* 构造MongoDB数据源<br>
|
||||
* 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败!
|
||||
*
|
||||
* @param host 主机(域名或者IP)
|
||||
* @param port 端口
|
||||
*/
|
||||
public MongoDS4(String host, int port) {
|
||||
this.serverAddress = createServerAddress(host, port);
|
||||
initSingle();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造MongoDB数据源<br>
|
||||
* 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败!
|
||||
*
|
||||
* @param mongoSetting MongoDB的配置文件,如果是null则读取默认配置文件或者使用MongoDB默认客户端配置
|
||||
* @param host 主机(域名或者IP)
|
||||
* @param port 端口
|
||||
*/
|
||||
public MongoDS4(Setting mongoSetting, String host, int port) {
|
||||
this.setting = mongoSetting;
|
||||
this.serverAddress = createServerAddress(host, port);
|
||||
initSingle();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造MongoDB数据源<br>
|
||||
* 当提供多个数据源时,这些数据源将为一个副本集或者多个mongos<br>
|
||||
* 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败! 官方文档: http://docs.mongodb.org/manual/administration/replica-sets/
|
||||
*
|
||||
* @param groups 分组列表,当为null或空时使用无分组配置,一个分组使用单一模式,否则使用副本集模式
|
||||
*/
|
||||
public MongoDS4(String... groups) {
|
||||
this.groups = groups;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造MongoDB数据源<br>
|
||||
* 当提供多个数据源时,这些数据源将为一个副本集或者mongos<br>
|
||||
* 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败!<br>
|
||||
* 官方文档: http://docs.mongodb.org/manual/administration/replica-sets/
|
||||
*
|
||||
* @param mongoSetting MongoDB的配置文件,必须有
|
||||
* @param groups 分组列表,当为null或空时使用无分组配置,一个分组使用单一模式,否则使用副本集模式
|
||||
*/
|
||||
public MongoDS4(Setting mongoSetting, String... groups) {
|
||||
if (mongoSetting == null) {
|
||||
throw new DbRuntimeException("Mongo setting is null!");
|
||||
}
|
||||
this.setting = mongoSetting;
|
||||
this.groups = groups;
|
||||
init();
|
||||
}
|
||||
// --------------------------------------------------------------------------- Constructor end
|
||||
|
||||
/**
|
||||
* 初始化,当给定分组数大于一个时使用
|
||||
*/
|
||||
public void init() {
|
||||
if (groups != null && groups.length > 1) {
|
||||
initCloud();
|
||||
} else {
|
||||
initSingle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化<br>
|
||||
* 设定文件中的host和端口有三种形式:
|
||||
*
|
||||
* <pre>
|
||||
* host = host:port
|
||||
* </pre>
|
||||
*
|
||||
* <pre>
|
||||
* host = host
|
||||
* port = port
|
||||
* </pre>
|
||||
*
|
||||
* <pre>
|
||||
* host = host
|
||||
* </pre>
|
||||
*/
|
||||
synchronized public void initSingle() {
|
||||
if (setting == null) {
|
||||
try {
|
||||
setting = new Setting(MONGO_CONFIG_PATH, true);
|
||||
} catch (Exception e) {
|
||||
// 在single模式下,可以没有配置文件。
|
||||
}
|
||||
}
|
||||
|
||||
String group = StrUtil.EMPTY;
|
||||
if (null == this.serverAddress) {
|
||||
//存在唯一分组
|
||||
if (groups != null && groups.length == 1) {
|
||||
group = groups[0];
|
||||
}
|
||||
serverAddress = createServerAddress(group);
|
||||
}
|
||||
|
||||
final MongoCredential credentail = createCredentail(group);
|
||||
try {
|
||||
MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder().applyToClusterSettings(b -> b.hosts(Collections.singletonList(serverAddress)));
|
||||
if (null != credentail) {
|
||||
clusterSettingsBuilder.credential(credentail);
|
||||
}
|
||||
mongo = new MongoClientImpl(clusterSettingsBuilder.build(), MongoDriverInformation.builder().build());
|
||||
} catch (Exception e) {
|
||||
throw new DbRuntimeException(StrUtil.format("Init MongoDB pool with connection to [{}] error!", serverAddress), e);
|
||||
}
|
||||
|
||||
log.info("Init MongoDB pool with connection to [{}]", serverAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化集群<br>
|
||||
* 集群的其它客户端设定参数使用全局设定<br>
|
||||
* 集群中每一个实例成员用一个group表示,例如:
|
||||
*
|
||||
* <pre>
|
||||
* user = test1
|
||||
* pass = 123456
|
||||
* database = test
|
||||
* [db0]
|
||||
* host = 192.168.1.1:27117
|
||||
* [db1]
|
||||
* host = 192.168.1.1:27118
|
||||
* [db2]
|
||||
* host = 192.168.1.1:27119
|
||||
* </pre>
|
||||
*/
|
||||
synchronized public void initCloud() {
|
||||
if (groups == null || groups.length == 0) {
|
||||
throw new DbRuntimeException("Please give replication set groups!");
|
||||
}
|
||||
|
||||
if (setting == null) {
|
||||
// 若未指定配置文件,则使用默认配置文件
|
||||
setting = new Setting(MONGO_CONFIG_PATH, true);
|
||||
}
|
||||
|
||||
final List<ServerAddress> addrList = new ArrayList<>();
|
||||
for (String group : groups) {
|
||||
addrList.add(createServerAddress(group));
|
||||
}
|
||||
|
||||
final MongoCredential credentail = createCredentail(StrUtil.EMPTY);
|
||||
try {
|
||||
MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder().applyToClusterSettings(b -> b.hosts(addrList));
|
||||
if (null != credentail) {
|
||||
clusterSettingsBuilder.credential(credentail);
|
||||
}
|
||||
mongo = new MongoClientImpl(clusterSettingsBuilder.build(), MongoDriverInformation.builder().build());
|
||||
} catch (Exception e) {
|
||||
log.error(e, "Init MongoDB connection error!");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Init MongoDB cloud Set pool with connection to {}", addrList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设定MongoDB配置文件
|
||||
*
|
||||
* @param setting 配置文件
|
||||
*/
|
||||
public void setSetting(Setting setting) {
|
||||
this.setting = setting;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 获得MongoDB客户端对象
|
||||
*/
|
||||
public MongoClient getMongo() {
|
||||
return mongo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得DB
|
||||
*
|
||||
* @param dbName DB
|
||||
* @return DB
|
||||
*/
|
||||
public MongoDatabase getDb(String dbName) {
|
||||
return mongo.getDatabase(dbName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得MongoDB中指定集合对象
|
||||
*
|
||||
* @param dbName 库名
|
||||
* @param collectionName 集合名
|
||||
* @return DBCollection
|
||||
*/
|
||||
public MongoCollection<Document> getCollection(String dbName, String collectionName) {
|
||||
return getDb(dbName).getCollection(collectionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mongo.close();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
* 创建ServerAddress对象,会读取配置文件中的相关信息
|
||||
*
|
||||
* @param group 分组,如果为{@code null}或者""默认为无分组
|
||||
* @return ServerAddress
|
||||
*/
|
||||
private ServerAddress createServerAddress(String group) {
|
||||
final Setting setting = checkSetting();
|
||||
|
||||
if (group == null) {
|
||||
group = StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
final String tmpHost = setting.getByGroup("host", group);
|
||||
if (StrUtil.isBlank(tmpHost)) {
|
||||
throw new NotInitedException("Host name is empy of group: {}", group);
|
||||
}
|
||||
|
||||
final int defaultPort = setting.getInt("port", group, 27017);
|
||||
return new ServerAddress(NetUtil.buildInetSocketAddress(tmpHost, defaultPort));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建ServerAddress对象
|
||||
*
|
||||
* @param host 主机域名或者IP(如果为空默认127.0.0.1)
|
||||
* @param port 端口(如果为空默认为)
|
||||
* @return ServerAddress
|
||||
*/
|
||||
private ServerAddress createServerAddress(String host, int port) {
|
||||
return new ServerAddress(host, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建{@link MongoCredential},用于服务端验证<br>
|
||||
* 此方法会首先读取指定分组下的属性,用户没有定义则读取空分组下的属性
|
||||
*
|
||||
* @param group 分组
|
||||
* @return {@link MongoCredential},如果用户未指定用户名密码返回null
|
||||
* @since 4.1.20
|
||||
*/
|
||||
private MongoCredential createCredentail(String group) {
|
||||
final Setting setting = this.setting;
|
||||
if (null == setting) {
|
||||
return null;
|
||||
}
|
||||
final String user = setting.getStr("user", group, setting.getStr("user"));
|
||||
final String pass = setting.getStr("pass", group, setting.getStr("pass"));
|
||||
final String database = setting.getStr("database", group, setting.getStr("database"));
|
||||
return createCredentail(user, database, pass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建{@link MongoCredential},用于服务端验证
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @param database 数据库名
|
||||
* @param password 密码
|
||||
* @return {@link MongoCredential}
|
||||
* @since 4.1.20
|
||||
*/
|
||||
private MongoCredential createCredentail(String userName, String database, String password) {
|
||||
if (StrUtil.hasEmpty(userName, database, database)) {
|
||||
return null;
|
||||
}
|
||||
return MongoCredential.createCredential(userName, database, password.toCharArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构件MongoDB连接选项<br>
|
||||
*
|
||||
* @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值
|
||||
* @return MongoClientOptions
|
||||
*/
|
||||
private MongoClientSettings buildMongoClientOptions(String group) {
|
||||
return buildMongoClientOptions(MongoClientSettings.builder(), group).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构件MongoDB连接选项<br>
|
||||
*
|
||||
* @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值
|
||||
* @return Builder
|
||||
*/
|
||||
private MongoClientSettings.Builder buildMongoClientOptions(MongoClientSettings.Builder builder, String group) {
|
||||
if (setting == null) {
|
||||
return builder;
|
||||
}
|
||||
|
||||
if (StrUtil.isEmpty(group)) {
|
||||
group = StrUtil.EMPTY;
|
||||
} else {
|
||||
group = group + StrUtil.DOT;
|
||||
}
|
||||
|
||||
// 每个主机答应的连接数(每个主机的连接池大小),当连接池被用光时,会被阻塞住
|
||||
Integer connectionsPerHost = setting.getInt(group + "connectionsPerHost");
|
||||
if (StrUtil.isBlank(group) == false && connectionsPerHost == null) {
|
||||
connectionsPerHost = setting.getInt("connectionsPerHost");
|
||||
}
|
||||
ConnectionPoolSettings.Builder connectionPoolSettingsBuilder = ConnectionPoolSettings.builder();
|
||||
if (connectionsPerHost != null) {
|
||||
connectionPoolSettingsBuilder.maxSize(connectionsPerHost);
|
||||
log.debug("MongoDB connectionsPerHost: {}", connectionsPerHost);
|
||||
}
|
||||
|
||||
// 被阻塞线程从连接池获取连接的最长等待时间(ms) --int
|
||||
Integer connectTimeout = setting.getInt(group + "connectTimeout");
|
||||
if (StrUtil.isBlank(group) == false && connectTimeout == null) {
|
||||
setting.getInt("connectTimeout");
|
||||
}
|
||||
if (connectTimeout != null) {
|
||||
connectionPoolSettingsBuilder.maxWaitTime(connectTimeout, TimeUnit.MILLISECONDS);
|
||||
log.debug("MongoDB connectTimeout: {}", connectTimeout);
|
||||
}
|
||||
builder.applyToConnectionPoolSettings(b -> b.applySettings(connectionPoolSettingsBuilder.build()));
|
||||
|
||||
// 套接字超时时间;该值会被传递给Socket.setSoTimeout(int)。默以为0(无穷) --int
|
||||
Integer socketTimeout = setting.getInt(group + "socketTimeout");
|
||||
if (StrUtil.isBlank(group) == false && socketTimeout == null) {
|
||||
setting.getInt("socketTimeout");
|
||||
}
|
||||
if (socketTimeout != null) {
|
||||
SocketSettings socketSettings = SocketSettings.builder().connectTimeout(socketTimeout, TimeUnit.MILLISECONDS).build();
|
||||
builder.applyToSocketSettings(b -> b.applySettings(socketSettings));
|
||||
log.debug("MongoDB socketTimeout: {}", socketTimeout);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Setting配置文件
|
||||
*
|
||||
* @return Setting配置文件
|
||||
*/
|
||||
private Setting checkSetting() {
|
||||
if (null == this.setting) {
|
||||
throw new DbRuntimeException("Please indicate setting file or create default [{}]", MONGO_CONFIG_PATH);
|
||||
}
|
||||
return this.setting;
|
||||
}
|
||||
// --------------------------------------------------------------------------- Private method end
|
||||
|
||||
}
|
@ -10,16 +10,20 @@ import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* MongoDB工厂类,用于创建
|
||||
* @author looly
|
||||
* {@link MongoDS}工厂类,用于创建
|
||||
*
|
||||
* @author Looly, VampireAchao
|
||||
*/
|
||||
public class MongoFactory {
|
||||
|
||||
/** 各分组做组合key的时候分隔符 */
|
||||
/**
|
||||
* 各分组做组合key的时候分隔符
|
||||
*/
|
||||
private final static String GROUP_SEPRATER = ",";
|
||||
|
||||
/** 数据源池 */
|
||||
/**
|
||||
* 数据源池
|
||||
*/
|
||||
private static final Map<String, MongoDS> DS_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
// JVM关闭前关闭MongoDB连接
|
||||
@ -28,6 +32,7 @@ public class MongoFactory {
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------ Get DS start
|
||||
|
||||
/**
|
||||
* 获取MongoDB数据源<br>
|
||||
*
|
||||
@ -80,7 +85,7 @@ public class MongoFactory {
|
||||
* 获取MongoDB数据源<br>
|
||||
*
|
||||
* @param setting 设定文件
|
||||
* @param groups 分组列表
|
||||
* @param groups 分组列表
|
||||
* @return MongoDB连接
|
||||
*/
|
||||
public static MongoDS getDS(Setting setting, String... groups) {
|
||||
@ -99,7 +104,7 @@ public class MongoFactory {
|
||||
* 获取MongoDB数据源<br>
|
||||
*
|
||||
* @param setting 配置文件
|
||||
* @param groups 分组列表
|
||||
* @param groups 分组列表
|
||||
* @return MongoDB连接
|
||||
*/
|
||||
public static MongoDS getDS(Setting setting, Collection<String> groups) {
|
||||
@ -111,8 +116,8 @@ public class MongoFactory {
|
||||
* 关闭全部连接
|
||||
*/
|
||||
public static void closeAll() {
|
||||
if(MapUtil.isNotEmpty(DS_MAP)){
|
||||
for(MongoDS ds : DS_MAP.values()) {
|
||||
if (MapUtil.isNotEmpty(DS_MAP)) {
|
||||
for (MongoDS ds : DS_MAP.values()) {
|
||||
ds.close();
|
||||
}
|
||||
DS_MAP.clear();
|
||||
|
@ -1,120 +0,0 @@
|
||||
package cn.hutool.db.nosql.mongo;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.RuntimeUtil;
|
||||
import cn.hutool.setting.Setting;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* MongoDB4工厂类,用于创建
|
||||
* @author VampireAchao
|
||||
*/
|
||||
public class MongoFactory4 {
|
||||
|
||||
/** 各分组做组合key的时候分隔符 */
|
||||
private final static String GROUP_SEPRATER = ",";
|
||||
|
||||
/** 数据源池 */
|
||||
private static final Map<String, MongoDS4> DS_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
// JVM关闭前关闭MongoDB连接
|
||||
static {
|
||||
RuntimeUtil.addShutdownHook(MongoFactory4::closeAll);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------ Get DS start
|
||||
/**
|
||||
* 获取MongoDB数据源<br>
|
||||
*
|
||||
* @param host 主机
|
||||
* @param port 端口
|
||||
* @return MongoDB连接
|
||||
*/
|
||||
public static MongoDS4 getDS(String host, int port) {
|
||||
final String key = host + ":" + port;
|
||||
MongoDS4 ds = DS_MAP.get(key);
|
||||
if (null == ds) {
|
||||
// 没有在池中加入之
|
||||
ds = new MongoDS4(host, port);
|
||||
DS_MAP.put(key, ds);
|
||||
}
|
||||
|
||||
return ds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取MongoDB数据源<br>
|
||||
* 多个分组名对应的连接组成集群
|
||||
*
|
||||
* @param groups 分组列表
|
||||
* @return MongoDB连接
|
||||
*/
|
||||
public static MongoDS4 getDS(String... groups) {
|
||||
final String key = ArrayUtil.join(groups, GROUP_SEPRATER);
|
||||
MongoDS4 ds = DS_MAP.get(key);
|
||||
if (null == ds) {
|
||||
// 没有在池中加入之
|
||||
ds = new MongoDS4(groups);
|
||||
DS_MAP.put(key, ds);
|
||||
}
|
||||
|
||||
return ds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取MongoDB数据源<br>
|
||||
*
|
||||
* @param groups 分组列表
|
||||
* @return MongoDB连接
|
||||
*/
|
||||
public static MongoDS4 getDS(Collection<String> groups) {
|
||||
return getDS(groups.toArray(new String[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取MongoDB数据源<br>
|
||||
*
|
||||
* @param setting 设定文件
|
||||
* @param groups 分组列表
|
||||
* @return MongoDB连接
|
||||
*/
|
||||
public static MongoDS4 getDS(Setting setting, String... groups) {
|
||||
final String key = setting.getSettingPath() + GROUP_SEPRATER + ArrayUtil.join(groups, GROUP_SEPRATER);
|
||||
MongoDS4 ds = DS_MAP.get(key);
|
||||
if (null == ds) {
|
||||
// 没有在池中加入之
|
||||
ds = new MongoDS4(setting, groups);
|
||||
DS_MAP.put(key, ds);
|
||||
}
|
||||
|
||||
return ds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取MongoDB数据源<br>
|
||||
*
|
||||
* @param setting 配置文件
|
||||
* @param groups 分组列表
|
||||
* @return MongoDB连接
|
||||
*/
|
||||
public static MongoDS4 getDS(Setting setting, Collection<String> groups) {
|
||||
return getDS(setting, groups.toArray(new String[0]));
|
||||
}
|
||||
// ------------------------------------------------------------------------ Get DS ends
|
||||
|
||||
/**
|
||||
* 关闭全部连接
|
||||
*/
|
||||
public static void closeAll() {
|
||||
if(MapUtil.isNotEmpty(DS_MAP)){
|
||||
for(MongoDS4 ds : DS_MAP.values()) {
|
||||
ds.close();
|
||||
}
|
||||
DS_MAP.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package cn.hutool.db.nosql;
|
||||
|
||||
import cn.hutool.db.nosql.mongo.MongoFactory4;
|
||||
import cn.hutool.db.nosql.mongo.MongoFactory;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
@ -13,8 +13,8 @@ public class MongoDBTest {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void redisDSTest() {
|
||||
MongoDatabase db = MongoFactory4.getDS("master").getDb("test");
|
||||
public void mongoDSTest() {
|
||||
MongoDatabase db = MongoFactory.getDS("master").getDb("test");
|
||||
Assert.assertEquals("test", db.getName());
|
||||
}
|
||||
}
|
||||
|
@ -13,8 +13,8 @@ socketKeepAlive=false
|
||||
|
||||
#---------------------------------- MongoDB实例连接
|
||||
[master]
|
||||
host = 127.0.0.1:27017
|
||||
host = localhost:27017
|
||||
|
||||
[slave]
|
||||
host = 127.0.0.1:27018
|
||||
host = localhost:27018
|
||||
#-----------------------------------------------------
|
||||
|
@ -1,3 +1,3 @@
|
||||
# Auto Configure
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.hutool.extra.spring.SpringUtil
|
||||
cn.hutool.extra.spring.SpringUtil
|
||||
|
@ -1161,10 +1161,11 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
|
||||
/**
|
||||
* 对于GET请求将参数加到URL中<br>
|
||||
* 此处不对URL中的特殊字符做单独编码
|
||||
* 此处不对URL中的特殊字符做单独编码<br>
|
||||
* 对于非rest的GET请求,且处于重定向时,参数丢弃
|
||||
*/
|
||||
private void urlWithParamIfGet() {
|
||||
if (Method.GET.equals(method) && false == this.isRest) {
|
||||
if (Method.GET.equals(method) && false == this.isRest && this.redirectCount > 0) {
|
||||
// 优先使用body形式的参数,不存在使用form
|
||||
if (ArrayUtil.isNotEmpty(this.bodyBytes)) {
|
||||
this.url.getQuery().parse(StrUtil.str(this.bodyBytes, this.charset), this.charset);
|
||||
@ -1194,7 +1195,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
|
||||
if (responseCode != HttpURLConnection.HTTP_OK) {
|
||||
if (HttpStatus.isRedirected(responseCode)) {
|
||||
setUrl(httpConnection.header(Header.LOCATION));
|
||||
setUrl(UrlBuilder.ofHttpWithoutEncode(httpConnection.header(Header.LOCATION)));
|
||||
if (redirectCount < this.maxRedirectCount) {
|
||||
redirectCount++;
|
||||
// 重定向不再走过滤器
|
||||
|
@ -0,0 +1,42 @@
|
||||
package cn.hutool.json;
|
||||
|
||||
import cn.hutool.core.annotation.Alias;
|
||||
import lombok.Data;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* https://gitee.com/dromara/hutool/issues/I4XFMW
|
||||
*/
|
||||
public class IssueI4XFMWTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
List<TestEntity> entityList = new ArrayList<>();
|
||||
TestEntity entityA = new TestEntity();
|
||||
entityA.setId("123");
|
||||
entityA.setPassword("456");
|
||||
entityList.add(entityA);
|
||||
|
||||
TestEntity entityB = new TestEntity();
|
||||
entityB.setId("789");
|
||||
entityB.setPassword("098");
|
||||
entityList.add(entityB);
|
||||
|
||||
String jsonStr = JSONUtil.toJsonStr(entityList);
|
||||
Assert.assertEquals("[{\"uid\":\"123\",\"password\":\"456\"},{\"uid\":\"789\",\"password\":\"098\"}]", jsonStr);
|
||||
List<TestEntity> testEntities = JSONUtil.toList(jsonStr, TestEntity.class);
|
||||
Assert.assertEquals("123", testEntities.get(0).getId());
|
||||
Assert.assertEquals("789", testEntities.get(1).getId());
|
||||
}
|
||||
|
||||
@Data
|
||||
static class TestEntity {
|
||||
@Alias("uid")
|
||||
private String id;
|
||||
private String password;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user