需求:
项目运营过程中不可避免的需要监控服务器指标,这时,使用 Java 代码远程调用 shell 脚本来获取服务器的一些信息,不失为一种常见的解决方案。
实现思路
使用 Java 代码模拟登录服务器,然后使用 shell 命令操作服务器。
必要说明
- 本 demo 为 SpringBoot 项目,JDK为1.8.0,IDE为IDEA2020.1。
- Java 代码调用的 shell 脚本已提前放置在服务器的相应位置。
- 若在使用 Java 代码调用 shell 脚本时,未传递服务器相关信息(
SSHVo
),则默认使用配置文件ssh.properties
中相应的参数操作服务器。
话不多说,直接上代码。
代码
代码结构
pom.xml
<!-- Java远程调用shell脚本 -->
<dependency>
<groupId>ch.ethz.ganymed</groupId>
<artifactId>ganymed-ssh2</artifactId>
<version>262</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
SSHVo 实体类
package com.demo.entity;
/**
* @Author: 瞎琢磨先生lyg
* @Date: 2020/4/4 12:10
* @Version 1.0
* Just do it, Just do IT
*/
public class SSHVo {
private String ipAddr;
private Integer port;
private String userName;
private String password;
private String cmds;
public SSHVo() {
}
public SSHVo(String ipAddr, Integer port, String userName, String password, String cmds) {
this.ipAddr = ipAddr;
this.port = port;
this.userName = userName;
this.password = password;
this.cmds = cmds;
}
public String getIpAddr() {
return ipAddr;
}
public void setIpAddr(String ipAddr) {
this.ipAddr = ipAddr;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getCmds() {
return cmds;
}
public void setCmds(String cmds) {
this.cmds = cmds;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"ipAddr\":\"")
.append(ipAddr).append('\"');
sb.append(",\"port\":")
.append(port);
sb.append(",\"userName\":\"")
.append(userName).append('\"');
sb.append(",\"password\":\"")
.append(password).append('\"');
sb.append(",\"cmds\":\"")
.append(cmds).append('\"');
sb.append('}');
return sb.toString();
}
}
SSH 工具类
SSH 连接、登录、操作服务器工具类
package com.demo.tool;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;
import com.demo.entity.SSHVo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* @Author: 瞎琢磨先生lyg
* @Date: 2020/4/4 12:10
* @Version 1.0
* Just do it, Just do IT
*/
@Component
@PropertySource(value = {"classpath:config/ssh.properties"})
public class SSHTool {
private static Charset charset = StandardCharsets.UTF_8;
private static Connection conn;
private static String ipAddr;
private static Integer port;
private static String userName;
private static String password;
private static String cmds;
@Value("${ipAddr}")
public void setIpAddr(String ipAddr) {
SSHTool.ipAddr = ipAddr;
}
@Value("${port}")
public void setPort(Integer port) {
SSHTool.port = port;
}
@Value("${userName}")
public void setUserName(String userName) {
SSHTool.userName = userName;
}
@Value("${password}")
public void setPassword(String password) {
SSHTool.password = password;
}
@Value("${cmds}")
public void setCmds(String cmds) {
SSHTool.cmds = cmds;
}
/**
* 登录远程Linux主机
*
* @return 是否登录成功
*/
private static boolean login(SSHVo sshVo) {
try {
// 连接
//conn = new Connection(ipAddr, port);
conn = new Connection(sshVo.getIpAddr(), sshVo.getPort());
conn.connect();
// 认证
return conn.authenticateWithPassword("root", sshVo.getPassword());
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 执行Shell脚本或命令
*
* @param sshVo .cmds 命令行序列
* @return 脚本输出结果
*/
public static StringBuilder exec(SSHVo sshVo) throws IOException {
InputStream in = null;
StringBuilder result = new StringBuilder();
if (sshVo == null || (sshVo != null && (sshVo.getIpAddr() == null || "".equals(sshVo.getIpAddr())))) {
sshVo = new SSHVo(ipAddr, port, userName, password, cmds);
}
System.out.println(sshVo
);
try {
if (SSHTool.login(sshVo)) {
// 打开一个会话
Session session = conn.openSession();
session.execCommand(sshVo.getCmds());
in = session.getStdout();
result = SSHTool.processStdout(in, SSHTool.charset);
conn.close();
}
} finally {
if (null != in) {
in.close();
}
}
return result;
}
/**
* 解析流获取字符串信息
*
* @param in 输入流对象
* @param charset 字符集
* @return 脚本输出结果
*/
public static StringBuilder processStdout(InputStream in, Charset charset) throws FileNotFoundException {
byte[] buf = new byte[1024];
StringBuilder sb = new StringBuilder();
// OutputStream os = new FileOutputStream("./data.txt");
try {
int length;
while ((length = in.read(buf)) != -1) {
// os.write(buf, 0, c);
sb.append(new String(buf, 0, length));
}
} catch (IOException e) {
e.printStackTrace();
}
return sb;
}
}
ssh.properties
(注意位置是在 resources/config
目录下)
ipAddr=124.160.108.89
port=22
userName=root
password=root
cmds=sh -x /test/shell.sh
测试类
public static void main(String[] args) throws IOException {
SSHTool.exec(new SSHVo(
"124.160.108.88",
22,
"root",
"root",
"mkdir -p /java/shell"));
}
访问 localhost:8080/shell
查看服务器,发现服务器上也调用了相应的 shell 脚本,说明 Java 代码调用 shell 脚本执行成功。
[注意事项]
- 使用Java代码调用shell脚本杀死当前服务,然后重启。无论尝试多少次,都无法进行重启。而单纯的使用shell实现杀死并重启服务却正常。
原因:Java代码调用shell杀死当前服务后,相当于调用shell脚本的主线程被杀了,子线程调用也就无从说起了。
解决办法:(曲线救国)。使用守护线程启动服务,当Java代码调用shell脚本杀死服务后,守护线程检测到服务被杀死,会自动将服务重启。
- 在win系统下编写的shell脚本极有可能无法在linux服务器上正常运行。
原因:就在于编码格式不一致。
解决办法:要么就在 linux 服务器上编写脚本,要么在win电脑上编写完毕后,使用 notepad++ 将其转码成linux的编码格式。
- 使用 shell 脚本调用 MySQL 脚本(eg. user.sql)报错,但是单独将其放在 Navicat 中执行,却一点错误都没有。
原因:SQL 脚本中存在以”#”开头的注释,导致 linux 上无法正常解析。
解决办法:将 SQL 脚本中所有以”#”开头的注释统统替换成”— “的注释(注意:—和待注释的内容之间要用”空格”隔开)。