//LinuxUtils
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.SCPClient;
import com.alipay.aqc.common.exception.BusinessException;
import com.alipay.aqc.common.exception.UnknownException;
import com.alipay.aqc.common.util.LinuxUtils.ITransferFileListener;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
/**
* 此类相比LinuxUtils类更新,主要是因为LinuxUtils类中读取命令执行结果的方法失效,所以新写一个类
* 如果有需要用到原来类的方法,但是读取不到结果,请在此类中使用新的api实现,把原来的方法标记为过时
*
*/
public class LinuxUtils2 {
private static Log logger = LogFactory.getLog(LinuxUtils2.class);
public static void main(String[] args) throws Exception {
String hostname = "tradecore-1-64.test.alipay.net";// 要登陆目标主机
String username = "admin";// 登陆用的用户名
String password = "jiaoyi123"; // 登陆用到的密码
/* Create a connection instance */
LinuxSession conn = getSession(hostname, username, password);
//
// //取远程文件
// File file = getRemoteFile(conn, "/home/admin/deploy.sh",
// "C:/一般文件/",null);
// if(file.exists()){
// System.out.println(CharsetUtils.guessCharset(file));
// }
List<String> findByFileNames = findByFileNames(conn,
"/home/admin/tradecore/target/tradecore.ace/core", new String[] { "*.jar" });
System.out.println(findByFileNames);
getRemoteDir(hostname, username, password,
"/home/admin/tradecore/target/tradecore.ace/core", "*.jar",
"/Users/xinkeliang/Downloads/jars");
close(conn);
}
public static interface CommandCallBack<T> {
void handle(T br) throws Exception;
}
static public class ResultToListCommandCallBack implements CommandCallBack<LinuxSession> {
private final List<String> resultLines;
public ResultToListCommandCallBack(List<String> resultLines) {
this.resultLines = resultLines;
}
@Override
public void handle(LinuxSession sess) {
ExecuteShellResult result = sess.result;
if (result.isHasException) {
resultLines.add(result.errorResult);
} else {
String resultString = result.result;
if (resultString != null) {
String[] lines = resultString.split("\\n");
resultLines.addAll(Arrays.asList(lines));
}
}
}
}
// private static List<String> findByFileNames(String host, String userName,
// String password, String remoteDir, String[] fileNames) {
// LinuxSession session = getConnection(host, userName, password);
// List<String> files = findByFileNames(session, remoteDir, fileNames);
// close(session);
// return files;
// }
private static List<String> findByFileNames(LinuxSession session, String remoteDir,
String[] fileNames) {
if (fileNames == null) {
return null;
}
final List<String> files = new ArrayList<String>();
for (int i = 0; i < fileNames.length; i++) {
String command = "find " + remoteDir + " -name "
+ getValidFileNamePattern(fileNames[i]);
execCommand(session, command, new ResultToListCommandCallBack(files));
}
return files;
}
/**
* 如果没有用引号括起来的,加上
* @param fileNamePattern
* @return
*/
static String getValidFileNamePattern(String fileNamePattern) {
if (fileNamePattern == null) {
return null;
}
StringBuilder sb = new StringBuilder(fileNamePattern);
if (!fileNamePattern.startsWith("\"") && !fileNamePattern.startsWith("'")) {
sb.insert(0, "\"");
}
if (!fileNamePattern.endsWith("\"") && !fileNamePattern.endsWith("'")) {
sb.append("\"");
}
return sb.toString();
}
/**
* 执行远程命令
* @param host
* @param userName
* @param password
* @param command
* @param commandEncoding 发送命令时使用的编码:utf-8 , gbk ....,结果读取时默认用gbk
*/
public static List<String> executeCommand(String host, String userName,
String password, String command, String commandEncoding, String resultEncoding) {
LinuxSession session = getSession(host, userName, password);
List<String> output = new ArrayList<String>();
execCommand(session, command, commandEncoding, resultEncoding, new LinuxUtils2.ResultToListCommandCallBack(output));
close(session);
return output;
}
/**
* 执行远程命令
* @param host
* @param userName
* @param password
* @param command
* @param callBack 可以为null
*/
public static void execCommand(String host, String userName,
String password, String command,
CommandCallBack<LinuxSession> callBack) {
LinuxSession session = getSession(host, userName, password);
execCommand(session, command, "UTF-8", "GBK", callBack);
close(session);
}
public static void execCommand(String host, String userName,
String password, String command, String commandEncoding,
CommandCallBack<LinuxSession> callBack) {
LinuxSession session = getSession(host, userName, password);
execCommand(session, command, commandEncoding, "GBK", callBack);
close(session);
}
static byte[] str2byte(String str, String encoding){
if(str==null)
return null;
try{ return str.getBytes(encoding); }
catch(java.io.UnsupportedEncodingException e){
return str.getBytes();
}
}
private static void execCommand(LinuxSession session, String command,
CommandCallBack<LinuxSession> callBack) {
//
execCommand(session, command, "UTF-8", "GBK", callBack);
}
private static void execCommand(LinuxSession session, String command, String commandEncoding, String resultEncoding,
CommandCallBack<LinuxSession> callBack) {
ExceptionUtils.throwIfEmpty(command, "错误:命令为空");
ChannelExec channelExec = null;
try {
ExecuteShellResult result = new ExecuteShellResult();
session.setResult(result);
channelExec = session.openChannel();
//channelExec.setCommand(command);
channelExec.setCommand(str2byte(command, commandEncoding));
ByteArrayOutputStream os = new ByteArrayOutputStream();
channelExec.setOutputStream(os);
ByteArrayOutputStream eos = new ByteArrayOutputStream();
channelExec.setErrStream(eos);
channelExec.connect();
if (logger.isDebugEnabled()) {
logger.debug("远程调用Shell 脚本:" + command);
}
// 等 0.1 秒.
TimeUnit.MILLISECONDS.sleep(100);
while (!channelExec.isClosed()) {
TimeUnit.SECONDS.sleep(5);
}
result.exitValue = channelExec.getExitStatus();
if (os != null && os.size() > 0) {
result.result = os.toString(resultEncoding);
}
if (eos != null && eos.size() > 0) {
result.errorResult = eos.toString();
}
if (callBack != null) {
callBack.handle(session);
}
os.close();
eos.close();
} catch (Exception e) {
if (isNoPermissionError(e)) {
throw new BusinessException("用户权限不够,请更换用户");
}
throw new UnknownException(e);
} finally {
if (channelExec != null)
channelExec.disconnect();
}
}
/**
* 取得链接
*
* @param host
* @param userName
* @param password
* @return
*/
private static LinuxSession getSession(String host, String userName, String password) {
ExceptionUtils.throwIfEmpty(host, "建立远程连接出错,远程地址不能为空");
return new LinuxSession(host, userName, password);
}
/**
* @author only openSession(),close can be used
*/
static public class LinuxSession {
com.jcraft.jsch.Session sshSession;
String host;
String userName;
String password;
int port = 22;
ExecuteShellResult result;
public LinuxSession(String host, String userName, String password) {
this.host = host;
this.userName = userName;
this.password = password;
createConnection();
}
public void setResult(ExecuteShellResult result) {
this.result = result;
}
public ChannelExec openChannel() {
return openChannel("exec");
}
public ChannelExec openChannel(String type) {
try {
return (ChannelExec) sshSession.openChannel(type);
} catch (JSchException e) {
throw new UnknownException("打开通道出错", e);
}
}
private void createConnection() {
try {
JSch jsch = new JSch();
sshSession = jsch.getSession(userName, host, port);
if (password != null) {
sshSession.setPassword(password);
} else {
String privateKey = "/home/" + userName + "/.ssh/id_rsa";
jsch.addIdentity(privateKey);
}
Properties sshConfig = new Properties();
// "StrictHostKeyChecking"如果设为"yes",
// ssh将不会自动把计算机的密匙加入"$HOME/.ssh/known_hosts"文件,
// 且一旦计算机的密匙发生了变化,就拒绝连接。
sshConfig.put("StrictHostKeyChecking", "no");
sshSession.setConfig(sshConfig);
sshSession.connect();
synchronized (sessions) {
sessions.put(this, System.currentTimeMillis());
}
} catch (Exception e) {
throw new BusinessException("建立远程连接出错:" + e.getMessage(), e);
}
}
public synchronized void close() {
sshSession.disconnect();
}
}
private static Map<LinuxSession, Long> sessions = new HashMap<LinuxSession, Long>();
private static class ConnectionMoniter implements Runnable {
private static final long MAX_ALIVE_TIME = 90 * 1000; // 最大存活时间90秒种
private static final List<LinuxSession> deadConnections = new ArrayList<LinuxSession>();
public void run() {
while (true) {
try {
synchronized (sessions) {
if (!sessions.isEmpty()) {
for (Iterator<LinuxSession> iterator = sessions.keySet().iterator(); iterator
.hasNext();) {
LinuxSession conn = iterator.next();
if ((System.currentTimeMillis() - sessions.get(conn)) >= MAX_ALIVE_TIME) {
deadConnections.add(conn);
}
}
}
}
if (deadConnections.size() > 0) {
for (Iterator<LinuxSession> iterator = deadConnections.iterator(); iterator
.hasNext();) {
LinuxUtils2.close(iterator.next());
iterator.remove();
}
}
} catch (Throwable e) {
// StringBuilder content = new StringBuilder();
// MailUtils.appendStackTrace(content, e);
// MailUtils.sendMessage("异常信息邮件:远程连接监视器发生错误",content.toString());
}
try {
Thread.sleep(5000);
} catch (Throwable e) {
}
}
}
}
static {// 全程监视联接情况
new Thread(new ConnectionMoniter()).start();
}
private static void close(Object connectionOrSession) {
if (connectionOrSession == null) {
return;
}
synchronized (sessions) {
sessions.remove(connectionOrSession);
if (connectionOrSession instanceof LinuxSession) {
((LinuxSession) connectionOrSession).close();
} else {
throw new UnknownException("未知的对象类型,无法关闭");
}
}
}
/**
* Shell 脚本执行结果.<br>
* 可以获取: <li>进程出口值. {@link ExecuteShellResult#getExitValue()} <br> <li>
* 进程输出流输出的结果. {@link ExecuteShellResult#getResult()} <br> <li>进程错误输出流输出的结果.
* {@link ExecuteShellResult#getErrorResult()} <br>
*/
public static class ExecuteShellResult {
/**
* 执行过程中没有异常.
*/
boolean isHasException;
/**
* 如果发生异常, cause 不能为空.
*/
// private Exception cause;
// /**
// * 进程的出口值。根据惯例,0 表示正常终止。
// */
int exitValue;
/**
* 进程输出流输出的结果.
*/
String result = "";
/**
* 进程错误输出流输出的结果.
*/
String errorResult = "";
}
public static void getRemoteDir(String host, String userName, String password,
String remoteDir, String fileName, String localDir) {
LinuxSession session = getSession(host, userName, password);
List<String> files = findByFileNames(session, remoteDir, new String[] { fileName });
close(session);
Connection connection = LinuxUtils.getConnection(host, userName, password);
if (!files.get(0).isEmpty()) {
getRemoteDir(connection, remoteDir, files, localDir, null);
}
LinuxUtils.close(connection);
}
private static File getRemoteDir(Connection connection, String remoteDir,
final List<String> files, String localDir,
ITransferFileListener listener) {
ExceptionUtils.throwIfEmpty(localDir, "错误:本地目录文件名为空");
File dir = new File(localDir);
if (files.size() == 0) {
return dir;//没有下载到有文件
}
//同名类,要先放起来,逐个取,因为取回来是放在同一目录的,所以不能一起取
List<String> tongMingLeis = new ArrayList<String>(5);
//装所有类名,用来判断是否重复
List<String> tempList = new ArrayList<String>(files.size());
//如果有内部类,把类名加入“\\”,否则取时会出错
for (int i = 0; i < files.size(); i++) {
String clazz = files.get(i);
if (clazz.contains("$")) {
files.set(i, clazz.replace("$", "\\$"));
}
String clazzName = clazz.replace('\\', '/');
//文件名
if (clazzName.contains("/")) {
clazzName = clazzName.substring(clazzName.lastIndexOf('/'));
}
if (tempList.contains(clazzName)) {
//加入同名类中
tongMingLeis.add(clazz);
//从批量取的集合中去除,后面另外单独取
files.remove(i);
i--;
}
//装到集合中
tempList.add(clazzName);
}
//用完了,释放集合
tempList = null;
//装到数组中
String[] array = files.toArray(new String[files.size()]);
//创建本地文件夹
if (!dir.exists()) {
dir.mkdirs();
}
//创建客户端
SCPClient client = new SCPClient(connection);
try {
//分批取文件,每批100个
int maxCount = 100;
String[] dest = new String[100];
for (int i = 0; (i * maxCount) < array.length; i++) {
if (array.length < ((i + 1) * maxCount)) {//改成分批进行,太多了SCP时会出错
dest = new String[array.length - i * maxCount];
}
System.arraycopy(array, i * maxCount, dest, 0, dest.length);
if (listener != null) {
listener.transferFileMsg("正在获取文件:" + dest[0]);
}
//取文件
client.get(dest, localDir);
}
//把文件从“dir”这个目录放回到原来的目录结构中
moveToOrigalPath(files, remoteDir, localDir);
//取同名类回来,同名类中不论是否多次同名,每次取一个,主要是为了编程简单,
for (String file : tongMingLeis) {
try {
//取回单个文件,放在当前目录中
getRemoteFile(connection, file, localDir, listener);
} catch (Exception e) {
if (isNoPermissionError(e)) {
throw new BusinessException("用户权限不够,请更换用户");
} else if (isNotARegularFile(e)) {//可能是一个文件夹,取不回来,那么直接在本址建文件夹好了
String filePath = file.replace(remoteDir, "");
filePath = filePath.replace("\\$", "$");
//以localDir为基准,恢复原来的层次结构
File toFile = new File(localDir + filePath);
toFile.mkdirs();
continue;
} else {
throw new UnknownException(e);
}
}
//把文件从“dir”这个目录放回到原来的目录结构中
moveToOrigalPath(Arrays.asList(new String[] { file }), remoteDir, localDir);
}
return dir;
} catch (IOException e) {
if (isNoPermissionError(e)) {
throw new BusinessException("用户权限不够,请更换用户");
}
throw new UnknownException(e);
}
}
/**
* 提示不规则文件错
* @param e
* @return
*/
static boolean isNotARegularFile(Throwable e) {
while (e != null) {
if (e.getMessage() != null && e.getMessage().contains("not a regular file")) {
return true;
}
e = e.getCause();
}
return false;
}
/**
* 把文件搬回到原来的位置
* 1、文件已全都放在localDir中,没有层次
* 2、把remoteFiles中的字符串删除掉remoteDir字符串,即得到文件本来的目录层次
* 3、将这个层次前面加上localDir中,即保留了原来的目录层次
* @param remoteFiles
* @param remoteDir
* @param localDir
*/
static void moveToOrigalPath(List<String> remoteFiles, String remoteDir, String localDir) {
if (remoteFiles.size() == 0) {
return;
}
for (String filePath : remoteFiles) {
//把remoteFiles中的字符串删除掉remoteDir字符串,即得到文件本来的目录层次
filePath = filePath.replace(remoteDir, "");
filePath = filePath.replace("\\$", "$");
filePath = filePath.replace('\\', '/');
//文件名
String fileName = filePath.substring(filePath.lastIndexOf('/'));
//文件已全都放在localDir中,没有层次,所以下面的代码可以得到当前文件绝对路径
File fromFile = new File(localDir + fileName);
//以localDir为基准,恢复原来的层次结构
File toFile = new File(localDir + filePath);
//建父文件夹
File parentFile = toFile.getParentFile();
if (!parentFile.exists()) {
parentFile.mkdirs();
}
try {
//移动文件
FileUtils.copyFile(fromFile, toFile);
com.alipay.aqc.common.util.FileUtils.deleteFile(fromFile);
} catch (IOException e) {
}
}
}
static boolean isNoPermissionError(Exception e) {
if (e == null) {
return false;
}
if (e.getMessage() == null) {
return false;
}
if (e.getMessage().contains("Permission denied")) {
return true;
}
if (e.getCause() != null && e.getCause().getMessage() != null
&& e.getCause().getMessage().contains("Permission denied")) {
return true;
}
return false;
}
public static boolean isEndWithPathSeperator(String dirName) {
if (dirName.endsWith("/")) {
return true;
}
if (dirName.endsWith("\\")) {
return true;
}
return false;
}
/**
* 取回单个文件
* @param connection
* @param fullFileName 不能是目录
* @param localDir
* @return
*/
private static File getRemoteFile(Connection connection, String fullFileName, String localDir,
ITransferFileListener listener) {
ExceptionUtils.throwIfEmpty(fullFileName, "错误:远程文件名为空");
ExceptionUtils.throwIfEmpty(localDir, "错误:本地目录文件名为空");
File dir = new File(localDir);
if (!dir.exists()) {
dir.mkdirs();
}
SCPClient client = new SCPClient(connection);
try {
if (listener != null) {
listener.transferFileMsg("正在获取文件:" + fullFileName);
}
client.get(fullFileName, localDir);
fullFileName = fullFileName.replace('\\', '/');
fullFileName = fullFileName.substring(fullFileName.lastIndexOf('/') + 1);
return new File(isEndWithPathSeperator(localDir) ? localDir + fullFileName
: localDir + File.separator + fullFileName);
} catch (IOException e) {
if (isNoPermissionError(e)) {
throw new BusinessException("用户权限不够,请更换用户");
}
throw new UnknownException(e);
}
}
}
|