Browse Source

add PinyinUtil

Looly 5 years ago
parent
commit
cf1f208aa0

+ 2 - 0
CHANGELOG.md

@@ -9,6 +9,8 @@
 * 【core   】     ImgUtil.createImage支持背景透明(issue#851@Github)
 * 【json   】     更改JSON转字符串时"</"被转义的规则为不转义(issue#852@Github)
 * 【cron   】     表达式的所有段支持L关键字(issue#849@Github)
+* 【extra  】     增加PinyinUtil,封装TinyPinyin
+* 【extra  】     Ftp和Sftp增加FtpConfig,提供超时等更多可选参数
 
 ### Bug修复
 * 【core   】     修复URLBuilder中请求参数有`&amp;`导致的问题(issue#850@Github)

+ 7 - 1
hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java

@@ -3,6 +3,7 @@ package cn.hutool.core.lang;
 import cn.hutool.core.lang.func.Func0;
 
 import java.io.Serializable;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.WeakHashMap;
 import java.util.concurrent.locks.StampedLock;
@@ -14,7 +15,7 @@ import java.util.concurrent.locks.StampedLock;
  * @param <V> 值类型
  * @author Looly
  */
-public class SimpleCache<K, V> implements Serializable {
+public class SimpleCache<K, V> implements Iterable<Map.Entry<K, V>>, Serializable {
 	private static final long serialVersionUID = 1L;
 
 	/**
@@ -148,4 +149,9 @@ public class SimpleCache<K, V> implements Serializable {
 			lock.unlockWrite(stamp);
 		}
 	}
+
+	@Override
+	public Iterator<Map.Entry<K, V>> iterator() {
+		return this.cache.entrySet().iterator();
+	}
 }

+ 6 - 0
hutool-extra/pom.xml

@@ -215,6 +215,12 @@
 			<optional>true</optional>
 		</dependency>
 		<dependency>
+			<groupId>io.github.biezhi</groupId>
+			<artifactId>TinyPinyin</artifactId>
+			<version>2.0.3.RELEASE</version>
+			<optional>true</optional>
+		</dependency>
+		<dependency>
 			<groupId>org.springframework.boot</groupId>
 			<artifactId>spring-boot-starter-test</artifactId>
 			<version>${spring-boot.version}</version>

+ 22 - 18
hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java

@@ -1,15 +1,15 @@
 package cn.hutool.extra.ftp;
 
-import java.io.Closeable;
-import java.io.File;
-import java.nio.charset.Charset;
-import java.util.List;
-
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.util.CharsetUtil;
 import cn.hutool.core.util.StrUtil;
 
+import java.io.Closeable;
+import java.io.File;
+import java.nio.charset.Charset;
+import java.util.List;
+
 /**
  * 抽象FTP类,用于定义通用的FTP方法
  * 
@@ -19,14 +19,18 @@ import cn.hutool.core.util.StrUtil;
 public abstract class AbstractFtp implements Closeable {
 	
 	public static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8 ;
-	
-	protected String host;
-	protected int port;
-	
-	protected String user;
-	protected String password;
-	
-	protected Charset charset;
+
+	protected FtpConfig ftpConfig;
+
+	/**
+	 * 构造
+	 *
+	 * @param config FTP配置
+	 * @since 5.3.3
+	 */
+	protected AbstractFtp(FtpConfig config){
+		this.ftpConfig = config;
+	}
 
 	/**
 	 * 如果连接超时的话,重新进行连接
@@ -119,12 +123,12 @@ public abstract class AbstractFtp implements Closeable {
 			//首位为空,表示以/开头
 			this.cd(StrUtil.SLASH);
 		}
-		for (int i = 0; i < dirs.length; i++) {
-			if (StrUtil.isNotEmpty(dirs[i])) {
-				if (false == cd(dirs[i])) {
+		for (String s : dirs) {
+			if (StrUtil.isNotEmpty(s)) {
+				if (false == cd(s)) {
 					//目录不存在时创建
-					mkdir(dirs[i]);
-					cd(dirs[i]);
+					mkdir(s);
+					cd(s);
 				}
 			}
 		}

+ 98 - 71
hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java

@@ -13,6 +13,7 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.SocketException;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.List;
@@ -20,23 +21,27 @@ import java.util.List;
 /**
  * FTP客户端封装<br>
  * 此客户端基于Apache-Commons-Net
- * 
+ *
  * @author looly
  * @since 4.1.8
  */
 public class Ftp extends AbstractFtp {
 
-	/** 默认端口 */
+	/**
+	 * 默认端口
+	 */
 	public static final int DEFAULT_PORT = 21;
 
 	private FTPClient client;
 	private FtpMode mode;
-	/** 执行完操作是否返回当前目录 */
+	/**
+	 * 执行完操作是否返回当前目录
+	 */
 	private boolean backToPwd;
 
 	/**
 	 * 构造,匿名登录
-	 * 
+	 *
 	 * @param host 域名或IP
 	 */
 	public Ftp(String host) {
@@ -45,7 +50,7 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 构造,匿名登录
-	 * 
+	 *
 	 * @param host 域名或IP
 	 * @param port 端口
 	 */
@@ -55,10 +60,10 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 构造
-	 * 
-	 * @param host 域名或IP
-	 * @param port 端口
-	 * @param user 用户名
+	 *
+	 * @param host     域名或IP
+	 * @param port     端口
+	 * @param user     用户名
 	 * @param password 密码
 	 */
 	public Ftp(String host, int port, String user, String password) {
@@ -67,12 +72,12 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 构造
-	 * 
-	 * @param host 域名或IP
-	 * @param port 端口
-	 * @param user 用户名
+	 *
+	 * @param host     域名或IP
+	 * @param port     端口
+	 * @param user     用户名
 	 * @param password 密码
-	 * @param charset 编码
+	 * @param charset  编码
 	 */
 	public Ftp(String host, int port, String user, String password, Charset charset) {
 		this(host, port, user, password, charset, null);
@@ -81,19 +86,25 @@ public class Ftp extends AbstractFtp {
 	/**
 	 * 构造
 	 *
-	 * @param host 域名或IP
-	 * @param port 端口
-	 * @param user 用户名
+	 * @param host     域名或IP
+	 * @param port     端口
+	 * @param user     用户名
 	 * @param password 密码
-	 * @param charset 编码
-	 * @param mode 模式
+	 * @param charset  编码
+	 * @param mode     模式
 	 */
 	public Ftp(String host, int port, String user, String password, Charset charset, FtpMode mode) {
-		this.host = host;
-		this.port = port;
-		this.user = user;
-		this.password = password;
-		this.charset = charset;
+		this(new FtpConfig(host, port, user, password, charset), mode);
+	}
+
+	/**
+	 * 构造
+	 *
+	 * @param config FTP配置
+	 * @param mode   模式
+	 */
+	public Ftp(FtpConfig config, FtpMode mode) {
+		super(config);
 		this.mode = mode;
 		this.init();
 	}
@@ -104,15 +115,15 @@ public class Ftp extends AbstractFtp {
 	 * @return this
 	 */
 	public Ftp init() {
-		return this.init(this.host, this.port, this.user, this.password, this.mode);
+		return this.init(this.ftpConfig, this.mode);
 	}
 
 	/**
 	 * 初始化连接
 	 *
-	 * @param host 域名或IP
-	 * @param port 端口
-	 * @param user 用户名
+	 * @param host     域名或IP
+	 * @param port     端口
+	 * @param user     用户名
 	 * @param password 密码
 	 * @return this
 	 */
@@ -122,22 +133,39 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 初始化连接
-	 * 
-	 * @param host 域名或IP
-	 * @param port 端口
-	 * @param user 用户名
+	 *
+	 * @param host     域名或IP
+	 * @param port     端口
+	 * @param user     用户名
 	 * @param password 密码
-	 * @param mode 模式
+	 * @param mode     模式
 	 * @return this
 	 */
 	public Ftp init(String host, int port, String user, String password, FtpMode mode) {
+		return init(new FtpConfig(host, port, user, password, this.ftpConfig.getCharset()), mode);
+	}
+
+	/**
+	 * 初始化连接
+	 *
+	 * @param config FTP配置
+	 * @param mode   模式
+	 * @return this
+	 */
+	public Ftp init(FtpConfig config, FtpMode mode) {
 		final FTPClient client = new FTPClient();
-		client.setControlEncoding(this.charset.toString());
+		client.setControlEncoding(config.getCharset().toString());
+		client.setConnectTimeout((int) config.getConnectionTimeout());
+		try {
+			client.setSoTimeout((int)config.getSoTimeout());
+		} catch (SocketException e) {
+			//ignore
+		}
 		try {
 			// 连接ftp服务器
-			client.connect(host, port);
+			client.connect(config.getHost(), config.getPort());
 			// 登录ftp服务器
-			client.login(user, password);
+			client.login(config.getUser(), config.getPassword());
 		} catch (IOException e) {
 			throw new FtpException(e);
 		}
@@ -148,7 +176,7 @@ public class Ftp extends AbstractFtp {
 			} catch (IOException e) {
 				// ignore
 			}
-			throw new FtpException("Login failed for user [{}], reply code is: [{}]", user, replyCode);
+			throw new FtpException("Login failed for user [{}], reply code is: [{}]", config.getUser(), replyCode);
 		}
 		this.client = client;
 		if (mode != null) {
@@ -159,7 +187,7 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 设置FTP连接模式,可选主动和被动模式
-	 * 
+	 *
 	 * @param mode 模式枚举
 	 * @return this
 	 * @since 4.1.19
@@ -167,19 +195,19 @@ public class Ftp extends AbstractFtp {
 	public Ftp setMode(FtpMode mode) {
 		this.mode = mode;
 		switch (mode) {
-		case Active:
-			this.client.enterLocalActiveMode();
-			break;
-		case Passive:
-			this.client.enterLocalPassiveMode();
-			break;
+			case Active:
+				this.client.enterLocalActiveMode();
+				break;
+			case Passive:
+				this.client.enterLocalPassiveMode();
+				break;
 		}
 		return this;
 	}
 
 	/**
 	 * 设置执行完操作是否返回当前目录
-	 * 
+	 *
 	 * @param backToPwd 执行完操作是否返回当前目录
 	 * @return this
 	 * @since 4.6.0
@@ -191,7 +219,7 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 如果连接超时的话,重新进行连接 经测试,当连接超时时,client.isConnected()仍然返回ture,无法判断是否连接超时 因此,通过发送pwd命令的方式,检查连接是否超时
-	 * 
+	 *
 	 * @return this
 	 */
 	@Override
@@ -211,7 +239,7 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 改变目录
-	 * 
+	 *
 	 * @param directory 目录
 	 * @return 是否成功
 	 */
@@ -230,7 +258,7 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 远程当前目录
-	 * 
+	 *
 	 * @return 远程当前目录
 	 * @since 4.1.14
 	 */
@@ -256,7 +284,7 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 遍历某个目录下所有文件和目录,不会递归遍历
-	 * 
+	 *
 	 * @param path 目录
 	 * @return 文件或目录列表
 	 */
@@ -291,7 +319,7 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 判断ftp服务器文件是否存在
-	 * 
+	 *
 	 * @param path 文件路径
 	 * @return 是否存在
 	 */
@@ -356,15 +384,15 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 上传文件到指定目录,可选:
-	 * 
+	 *
 	 * <pre>
 	 * 1. path为null或""上传到当前路径
 	 * 2. path为相对路径则相对于当前路径的子路径
 	 * 3. path为绝对路径则上传到此路径
 	 * </pre>
-	 * 
+	 *
 	 * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径
-	 * @param file 文件
+	 * @param file     文件
 	 * @return 是否上传成功
 	 */
 	@Override
@@ -375,15 +403,15 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 上传文件到指定目录,可选:
-	 * 
+	 *
 	 * <pre>
 	 * 1. path为null或""上传到当前路径
 	 * 2. path为相对路径则相对于当前路径的子路径
 	 * 3. path为绝对路径则上传到此路径
 	 * </pre>
-	 * 
-	 * @param file 文件
-	 * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径
+	 *
+	 * @param file     文件
+	 * @param path     服务端路径,可以为{@code null} 或者相对路径或绝对路径
 	 * @param fileName 自定义在服务端保存的文件名
 	 * @return 是否上传成功
 	 */
@@ -397,16 +425,15 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 上传文件到指定目录,可选:
-	 * 
+	 *
 	 * <pre>
 	 * 1. path为null或""上传到当前路径
 	 * 2. path为相对路径则相对于当前路径的子路径
 	 * 3. path为绝对路径则上传到此路径
 	 * </pre>
-	 * 
-	 * 
-	 * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径
-	 * @param fileName 文件名
+	 *
+	 * @param path       服务端路径,可以为{@code null} 或者相对路径或绝对路径
+	 * @param fileName   文件名
 	 * @param fileStream 文件流
 	 * @return 是否上传成功
 	 */
@@ -443,8 +470,8 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 下载文件
-	 * 
-	 * @param path 文件路径
+	 *
+	 * @param path    文件路径
 	 * @param outFile 输出文件或目录
 	 */
 	@Override
@@ -456,10 +483,10 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 下载文件
-	 * 
-	 * @param path 文件路径
+	 *
+	 * @param path     文件路径
 	 * @param fileName 文件名
-	 * @param outFile 输出文件或目录
+	 * @param outFile  输出文件或目录
 	 */
 	public void download(String path, String fileName, File outFile) {
 		if (outFile.isDirectory()) {
@@ -477,10 +504,10 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 下载文件到输出流
-	 * 
-	 * @param path 文件路径
+	 *
+	 * @param path     文件路径
 	 * @param fileName 文件名
-	 * @param out 输出位置
+	 * @param out      输出位置
 	 */
 	public void download(String path, String fileName, OutputStream out) {
 		String pwd = null;
@@ -503,7 +530,7 @@ public class Ftp extends AbstractFtp {
 
 	/**
 	 * 获取FTPClient客户端对象
-	 * 
+	 *
 	 * @return {@link FTPClient}
 	 */
 	public FTPClient getClient() {

+ 134 - 0
hutool-extra/src/main/java/cn/hutool/extra/ftp/FtpConfig.java

@@ -0,0 +1,134 @@
+package cn.hutool.extra.ftp;
+
+import java.io.Serializable;
+import java.nio.charset.Charset;
+
+/**
+ * FTP配置项,提供FTP各种参数信息
+ *
+ * @author looly
+ */
+public class FtpConfig implements Serializable {
+	private static final long serialVersionUID = 1L;
+
+	public static FtpConfig create(){
+		return new FtpConfig();
+	}
+
+	/**
+	 * 主机
+	 */
+	private String host;
+	/**
+	 * 端口
+	 */
+	private int port;
+	/**
+	 * 用户名
+	 */
+	private String user;
+	/**
+	 * 密码
+	 */
+	private String password;
+	/**
+	 * 编码
+	 */
+	private Charset charset;
+
+	/**
+	 * 连接超时时长,单位毫秒
+	 */
+	private long connectionTimeout;
+
+	/**
+	 * Socket连接超时时长,单位毫秒
+	 */
+	private long soTimeout;
+
+	/**
+	 * 构造
+	 */
+	public FtpConfig() {
+	}
+
+	/**
+	 * 构造
+	 *
+	 * @param host 主机
+	 * @param port 端口
+	 * @param user 用户名
+	 * @param password 密码
+	 * @param charset 编码
+	 */
+	public FtpConfig(String host, int port, String user, String password, Charset charset) {
+		this.host = host;
+		this.port = port;
+		this.user = user;
+		this.password = password;
+		this.charset = charset;
+	}
+
+	public String getHost() {
+		return host;
+	}
+
+	public FtpConfig setHost(String host) {
+		this.host = host;
+		return this;
+	}
+
+	public int getPort() {
+		return port;
+	}
+
+	public FtpConfig setPort(int port) {
+		this.port = port;
+		return this;
+	}
+
+	public String getUser() {
+		return user;
+	}
+
+	public FtpConfig setUser(String user) {
+		this.user = user;
+		return this;
+	}
+
+	public String getPassword() {
+		return password;
+	}
+
+	public FtpConfig setPassword(String password) {
+		this.password = password;
+		return this;
+	}
+
+	public Charset getCharset() {
+		return charset;
+	}
+
+	public FtpConfig setCharset(Charset charset) {
+		this.charset = charset;
+		return this;
+	}
+
+	public long getConnectionTimeout() {
+		return connectionTimeout;
+	}
+
+	public FtpConfig setConnectionTimeout(long connectionTimeout) {
+		this.connectionTimeout = connectionTimeout;
+		return this;
+	}
+
+	public long getSoTimeout() {
+		return soTimeout;
+	}
+
+	public FtpConfig setSoTimeout(long soTimeout) {
+		this.soTimeout = soTimeout;
+		return this;
+	}
+}

+ 73 - 0
hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java

@@ -0,0 +1,73 @@
+package cn.hutool.extra.pinyin;
+
+import com.github.promeg.pinyinhelper.Pinyin;
+
+/**
+ * 拼音工具类,封装了TinyPinyin
+ *
+ * <p>
+ * TinyPinyin(https://github.com/promeG/TinyPinyin)提供者未提交Maven中央库,<br>
+ * 因此使用
+ * https://github.com/biezhi/TinyPinyin打包的版本
+ * </p>
+ *
+ * <p>
+ * 引入:
+ * <pre>
+ * &lt;dependency&gt;
+ *     &lt;groupId&gt;io.github.biezhi&lt;/groupId&gt;
+ *     &lt;artifactId&gt;TinyPinyin&lt;/artifactId&gt;
+ *     &lt;version&gt;2.0.3.RELEASE&lt;/version&gt;
+ * &lt;/dependency&gt;
+ * </pre>
+ * </p>
+ *
+ * @author looly
+ */
+public class PinyinUtil {
+
+	/**
+	 * 自定义拼音全局配置,例如加入自定义字典等
+	 *
+	 * @param config 配置,通过Pinyin.newConfig().with(dict)添加字典
+	 */
+	public static void init(Pinyin.Config config) {
+		Pinyin.init(config);
+	}
+
+	/**
+	 * 如果c为汉字,则返回大写拼音;如果c不是汉字,则返回String.valueOf(c)
+	 *
+	 * @param c             任意字符,汉族返回拼音,非汉字原样返回
+	 * @param isToUpperCase 是否转换为大写
+	 * @return 汉族返回拼音,非汉字原样返回
+	 */
+	public static String toPinyin(char c, boolean isToUpperCase) {
+		final String pinyin = Pinyin.toPinyin(c);
+		return isToUpperCase ? pinyin : pinyin.toLowerCase();
+	}
+
+	/**
+	 * 将输入字符串转为拼音,每个字之间的拼音使用空格分隔
+	 *
+	 * @param str           任意字符,汉族返回拼音,非汉字原样返回
+	 * @param isToUpperCase 是否转换为大写
+	 * @return 汉族返回拼音,非汉字原样返回
+	 */
+	public static String toPinyin(String str, boolean isToUpperCase) {
+		return toPinyin(str, " ", isToUpperCase);
+	}
+
+	/**
+	 * 将输入字符串转为拼音,以字符为单位插入分隔符
+	 *
+	 * @param str           任意字符,汉族返回拼音,非汉字原样返回
+	 * @param separator     每个字拼音之间的分隔符
+	 * @param isToUpperCase 是否转换为大写
+	 * @return 汉族返回拼音,非汉字原样返回
+	 */
+	public static String toPinyin(String str, String separator, boolean isToUpperCase) {
+		final String pinyin = Pinyin.toPinyin(str, separator);
+		return isToUpperCase ? pinyin : pinyin.toLowerCase();
+	}
+}

+ 7 - 0
hutool-extra/src/main/java/cn/hutool/extra/pinyin/package-info.java

@@ -0,0 +1,7 @@
+/**
+ * 拼音工具封装,基于TinyPinyin
+ * 
+ * @author looly
+ *
+ */
+package cn.hutool.extra.pinyin;

+ 15 - 39
hutool-extra/src/main/java/cn/hutool/extra/ssh/JschSessionPool.java

@@ -1,13 +1,12 @@
 package cn.hutool.extra.ssh;
 
+import cn.hutool.core.lang.SimpleCache;
 import cn.hutool.core.util.StrUtil;
 import com.jcraft.jsch.Session;
 
-import java.util.Collection;
+import java.util.HashMap;
 import java.util.Iterator;
-import java.util.Map;
 import java.util.Map.Entry;
-import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Jsch会话池
@@ -20,11 +19,7 @@ public enum JschSessionPool {
 	/**
 	 * SSH会话池,key:host,value:Session对象
 	 */
-	private final Map<String, Session> sessionPool = new ConcurrentHashMap<>();
-	/**
-	 * 锁
-	 */
-	private static final Object lock = new Object();
+	private final SimpleCache<String, Session> cache = new SimpleCache<>(new HashMap<>());
 
 	/**
 	 * 获取Session,不存在返回null
@@ -33,7 +28,7 @@ public enum JschSessionPool {
 	 * @return Session
 	 */
 	public Session get(String key) {
-		return sessionPool.get(key);
+		return cache.get(key);
 	}
 
 	/**
@@ -47,17 +42,7 @@ public enum JschSessionPool {
 	 */
 	public Session getSession(String sshHost, int sshPort, String sshUser, String sshPass) {
 		final String key = StrUtil.format("{}@{}:{}", sshUser, sshHost, sshPort);
-		Session session = get(key);
-		if (null == session || false == session.isConnected()) {
-			synchronized (lock) {
-				session = get(key);
-				if (null == session || false == session.isConnected()) {
-					session = JschUtil.openSession(sshHost, sshPort, sshUser, sshPass);
-					put(key, session);
-				}
-			}
-		}
-		return session;
+		return this.cache.get(key, ()-> JschUtil.openSession(sshHost, sshPort, sshUser, sshPass));
 	}
 
 	/**
@@ -72,17 +57,7 @@ public enum JschSessionPool {
 	 */
 	public Session getSession(String sshHost, int sshPort, String sshUser, String prvkey, byte[] passphrase) {
 		final String key = StrUtil.format("{}@{}:{}", sshUser, sshHost, sshPort);
-		Session session = get(key);
-		if (null == session || false == session.isConnected()) {
-			synchronized (lock) {
-				session = get(key);
-				if (null == session || false == session.isConnected()) {
-					session = JschUtil.openSession(sshHost, sshPort, sshUser, prvkey, passphrase);
-					put(key, session);
-				}
-			}
-		}
-		return session;
+		return this.cache.get(key, ()->JschUtil.openSession(sshHost, sshPort, sshUser, prvkey, passphrase));
 	}
 
 	/**
@@ -92,7 +67,7 @@ public enum JschSessionPool {
 	 * @param session Session
 	 */
 	public void put(String key, Session session) {
-		this.sessionPool.put(key, session);
+		this.cache.put(key, session);
 	}
 
 	/**
@@ -101,11 +76,11 @@ public enum JschSessionPool {
 	 * @param key 主机,格式为user@host:port
 	 */
 	public void close(String key) {
-		Session session = sessionPool.get(key);
+		Session session = get(key);
 		if (session != null && session.isConnected()) {
 			session.disconnect();
 		}
-		sessionPool.remove(key);
+		this.cache.remove(key);
 	}
 
 	/**
@@ -116,7 +91,7 @@ public enum JschSessionPool {
 	 */
 	public void remove(Session session) {
 		if (null != session) {
-			final Iterator<Entry<String, Session>> iterator = this.sessionPool.entrySet().iterator();
+			final Iterator<Entry<String, Session>> iterator = this.cache.iterator();
 			Entry<String, Session> entry;
 			while (iterator.hasNext()) {
 				entry = iterator.next();
@@ -132,12 +107,13 @@ public enum JschSessionPool {
 	 * 关闭所有SSH连接会话
 	 */
 	public void closeAll() {
-		Collection<Session> sessions = sessionPool.values();
-		for (Session session : sessions) {
-			if (session.isConnected()) {
+		Session session;
+		for (Entry<String, Session> entry : this.cache) {
+			session = entry.getValue();
+			if (session != null && session.isConnected()) {
 				session.disconnect();
 			}
 		}
-		sessionPool.clear();
+		cache.clear();
 	}
 }

+ 43 - 3
hutool-extra/src/main/java/cn/hutool/extra/ssh/JschUtil.java

@@ -85,9 +85,24 @@ public class JschUtil {
 	 * @return SSH会话
 	 */
 	public static Session openSession(String sshHost, int sshPort, String sshUser, String sshPass) {
+		return openSession(sshHost, sshPort, sshUser, sshPass, 0);
+	}
+
+	/**
+	 * 打开一个新的SSH会话
+	 *
+	 * @param sshHost 主机
+	 * @param sshPort 端口
+	 * @param sshUser 用户名
+	 * @param sshPass 密码
+	 * @param timeout Socket连接超时时长,单位毫秒
+	 * @return SSH会话
+	 * @since 5.3.3
+	 */
+	public static Session openSession(String sshHost, int sshPort, String sshUser, String sshPass, int timeout) {
 		final Session session = createSession(sshHost, sshPort, sshUser, sshPass);
 		try {
-			session.connect();
+			session.connect(timeout);
 		} catch (JSchException e) {
 			throw new JschRuntimeException(e);
 		}
@@ -257,7 +272,19 @@ public class JschUtil {
 	 * @since 4.0.3
 	 */
 	public static ChannelSftp openSftp(Session session) {
-		return (ChannelSftp) openChannel(session, ChannelType.SFTP);
+		return openSftp(session, 0);
+	}
+
+	/**
+	 * 打开SFTP连接
+	 *
+	 * @param session Session会话
+	 * @param timeout 连接超时时长,单位毫秒
+	 * @return {@link ChannelSftp}
+	 * @since 5.3.3
+	 */
+	public static ChannelSftp openSftp(Session session, int timeout) {
+		return (ChannelSftp) openChannel(session, ChannelType.SFTP, timeout);
 	}
 
 	/**
@@ -305,9 +332,22 @@ public class JschUtil {
 	 * @since 4.5.2
 	 */
 	public static Channel openChannel(Session session, ChannelType channelType) {
+		return openChannel(session, channelType, 0);
+	}
+
+	/**
+	 * 打开Channel连接
+	 *
+	 * @param session     Session会话
+	 * @param channelType 通道类型,可以是shell或sftp等,见{@link ChannelType}
+	 * @param timeout     连接超时时长,单位毫秒
+	 * @return {@link Channel}
+	 * @since 5.3.3
+	 */
+	public static Channel openChannel(Session session, ChannelType channelType, int timeout) {
 		final Channel channel = createChannel(session, channelType);
 		try {
-			channel.connect();
+			channel.connect(Math.max(timeout, 0));
 		} catch (JSchException e) {
 			throw new JschRuntimeException(e);
 		}

+ 41 - 12
hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java

@@ -4,6 +4,7 @@ import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.lang.Filter;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.extra.ftp.AbstractFtp;
+import cn.hutool.extra.ftp.FtpConfig;
 import com.jcraft.jsch.ChannelSftp;
 import com.jcraft.jsch.ChannelSftp.LsEntry;
 import com.jcraft.jsch.ChannelSftp.LsEntrySelector;
@@ -59,7 +60,18 @@ public class Sftp extends AbstractFtp {
 	 * @since 4.1.14
 	 */
 	public Sftp(String sshHost, int sshPort, String sshUser, String sshPass, Charset charset) {
-		init(sshHost, sshPort, sshUser, sshPass, charset);
+		this(new FtpConfig(sshHost, sshPort, sshUser, sshPass, charset));
+	}
+
+	/**
+	 * 构造
+	 *
+	 * @param config FTP配置
+	 * @since 5.3.3
+	 */
+	public Sftp(FtpConfig config) {
+		super(config);
+		init(config);
 	}
 
 	/**
@@ -79,6 +91,7 @@ public class Sftp extends AbstractFtp {
 	 * @since 4.1.14
 	 */
 	public Sftp(Session session, Charset charset) {
+		super(FtpConfig.create().setCharset(charset));
 		init(session, charset);
 	}
 
@@ -89,6 +102,7 @@ public class Sftp extends AbstractFtp {
 	 * @param charset 编码
 	 */
 	public Sftp(ChannelSftp channel, Charset charset) {
+		super(FtpConfig.create().setCharset(charset));
 		init(channel, charset);
 	}
 	// ---------------------------------------------------------------------------------------- Constructor end
@@ -103,22 +117,37 @@ public class Sftp extends AbstractFtp {
 	 * @param charset 编码
 	 */
 	public void init(String sshHost, int sshPort, String sshUser, String sshPass, Charset charset) {
-		this.host = sshHost;
-		this.port = sshPort;
-		this.user = sshUser;
-		this.password = sshPass;
 		init(JschUtil.getSession(sshHost, sshPort, sshUser, sshPass), charset);
 	}
 
 	/**
 	 * 初始化
+	 *
+	 * @since 5.3.3
+	 */
+	public void init() {
+		init(this.ftpConfig);
+	}
+
+	/**
+	 * 初始化
+	 *
+	 * @param config FTP配置
+	 * @since 5.3.3
+	 */
+	public void init(FtpConfig config) {
+		init(config.getHost(), config.getPort(), config.getUser(), config.getPassword(), config.getCharset());
+	}
+
+	/**
+	 * 初始化
 	 * 
 	 * @param session {@link Session}
 	 * @param charset 编码
 	 */
 	public void init(Session session, Charset charset) {
 		this.session = session;
-		init(JschUtil.openSftp(session), charset);
+		init(JschUtil.openSftp(session, (int)this.ftpConfig.getConnectionTimeout()), charset);
 	}
 
 	/**
@@ -128,7 +157,7 @@ public class Sftp extends AbstractFtp {
 	 * @param charset 编码
 	 */
 	public void init(ChannelSftp channel, Charset charset) {
-		this.charset = charset;
+		this.ftpConfig.setCharset(charset);
 		try {
 			channel.setFilenameEncoding(charset.toString());
 		} catch (SftpException e) {
@@ -139,8 +168,8 @@ public class Sftp extends AbstractFtp {
 
 	@Override
 	public Sftp reconnectIfTimeout() {
-		if (false == this.cd("/") && StrUtil.isNotBlank(this.host)) {
-			init(this.host, this.port, this.user, this.password, this.charset);
+		if (false == this.cd("/") && StrUtil.isNotBlank(this.ftpConfig.getHost())) {
+			init(this.ftpConfig);
 		}
 		return this;
 	}
@@ -414,9 +443,9 @@ public class Sftp extends AbstractFtp {
 	@Override
 	public String toString() {
 		return "Sftp{" +
-				"host='" + host + '\'' +
-				", port=" + port +
-				", user='" + user + '\'' +
+				"host='" + this.ftpConfig.getHost() + '\'' +
+				", port=" + this.ftpConfig.getPort() +
+				", user='" + this.ftpConfig.getUser() + '\'' +
 				'}';
 	}
 

+ 19 - 0
hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java

@@ -0,0 +1,19 @@
+package cn.hutool.extra.pinyin;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PinyinUtilTest {
+
+	@Test
+	public void toPinyinTest(){
+		final String pinyin = PinyinUtil.toPinyin("你好", false);
+		Assert.assertEquals("ni hao", pinyin);
+	}
+
+	@Test
+	public void toPinyinUpperCaseTest(){
+		final String pinyin = PinyinUtil.toPinyin("你好怡", true);
+		Assert.assertEquals("NI HAO YI", pinyin);
+	}
+}