Merge remote-tracking branch 'origin/v5-dev' into v5-dev

This commit is contained in:
VampireAchao 2022-03-15 22:57:48 +08:00
commit e1de291063
25 changed files with 361 additions and 701 deletions

View File

@ -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 】 增加UniqueKeySetissue#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)

View File

@ -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)

View File

@ -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)

View File

@ -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, '零');
}
}

View File

@ -700,7 +700,7 @@ public class FileUtil extends PathUtil {
if (null == file) {
return null;
}
return mkdir(file.getParentFile());
return mkdir(getParent(file, 1));
}
/**

View File

@ -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);

View File

@ -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
*/

View File

@ -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);
}

View File

@ -84,13 +84,13 @@ public final class CsvParser extends ComputeIter<CsvRow> implements Closeable, S
}
/**
* 获取头部字段列表如果containsHeader设置为false则抛出异常
* 获取头部字段列表如果headerLineNo &lt; 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;

View File

@ -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;
}

View File

@ -368,6 +368,48 @@ public class ArrayUtil extends PrimitiveArrayUtil {
}
}
/**
* 将新元素插入到到已有数组中的某个位置<br>
* 添加新元素会生成一个新数组或原有数组<br>
* 如果插入位置为为负数那么生成一个由插入元素顺序加已有数组顺序的新数组
*
* @param <T> 数组元素类型
* @param buffer 已有数组
* @param index 位置大于长度追加否则替换&lt;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
*/

View File

@ -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);
}
}

View File

@ -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)));
}
}

View File

@ -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);
}
}

View File

@ -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() {
//字符

View File

@ -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>

View File

@ -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
}

View File

@ -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
}

View File

@ -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();

View File

@ -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();
}
}
}

View File

@ -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());
}
}

View File

@ -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
#-----------------------------------------------------

View File

@ -1,3 +1,3 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.hutool.extra.spring.SpringUtil
cn.hutool.extra.spring.SpringUtil

View File

@ -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++;
// 重定向不再走过滤器

View File

@ -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;
}
}