需求:

项目运营过程中不可避免的需要监控服务器指标,这时,使用 Java 代码远程调用 shell 脚本来获取服务器的一些信息,不失为一种常见的解决方案。

实现思路

使用 Java 代码模拟登录服务器,然后使用 shell 命令操作服务器。

必要说明

  • 本 demo 为 SpringBoot 项目,JDK为1.8.0,IDE为IDEA2020.1。
  • Java 代码调用的 shell 脚本已提前放置在服务器的相应位置。
  • 若在使用 Java 代码调用 shell 脚本时,未传递服务器相关信息(SSHVo),则默认使用配置文件 ssh.properties 中相应的参数操作服务器。

话不多说,直接上代码。

代码

代码结构

Java代码远程调用shell脚本 - 图1

pom.xml

  1. <!-- Java远程调用shell脚本 -->
  2. <dependency>
  3. <groupId>ch.ethz.ganymed</groupId>
  4. <artifactId>ganymed-ssh2</artifactId>
  5. <version>262</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>commons-io</groupId>
  9. <artifactId>commons-io</artifactId>
  10. <version>2.6</version>
  11. </dependency>

SSHVo 实体类

  1. package com.demo.entity;
  2. /**
  3. * @Author: 瞎琢磨先生lyg
  4. * @Date: 2020/4/4 12:10
  5. * @Version 1.0
  6. * Just do it, Just do IT
  7. */
  8. public class SSHVo {
  9. private String ipAddr;
  10. private Integer port;
  11. private String userName;
  12. private String password;
  13. private String cmds;
  14. public SSHVo() {
  15. }
  16. public SSHVo(String ipAddr, Integer port, String userName, String password, String cmds) {
  17. this.ipAddr = ipAddr;
  18. this.port = port;
  19. this.userName = userName;
  20. this.password = password;
  21. this.cmds = cmds;
  22. }
  23. public String getIpAddr() {
  24. return ipAddr;
  25. }
  26. public void setIpAddr(String ipAddr) {
  27. this.ipAddr = ipAddr;
  28. }
  29. public Integer getPort() {
  30. return port;
  31. }
  32. public void setPort(Integer port) {
  33. this.port = port;
  34. }
  35. public String getUserName() {
  36. return userName;
  37. }
  38. public void setUserName(String userName) {
  39. this.userName = userName;
  40. }
  41. public String getPassword() {
  42. return password;
  43. }
  44. public void setPassword(String password) {
  45. this.password = password;
  46. }
  47. public String getCmds() {
  48. return cmds;
  49. }
  50. public void setCmds(String cmds) {
  51. this.cmds = cmds;
  52. }
  53. @Override
  54. public String toString() {
  55. final StringBuilder sb = new StringBuilder("{");
  56. sb.append("\"ipAddr\":\"")
  57. .append(ipAddr).append('\"');
  58. sb.append(",\"port\":")
  59. .append(port);
  60. sb.append(",\"userName\":\"")
  61. .append(userName).append('\"');
  62. sb.append(",\"password\":\"")
  63. .append(password).append('\"');
  64. sb.append(",\"cmds\":\"")
  65. .append(cmds).append('\"');
  66. sb.append('}');
  67. return sb.toString();
  68. }
  69. }

SSH 工具类

SSH 连接、登录、操作服务器工具类

  1. package com.demo.tool;
  2. import ch.ethz.ssh2.Connection;
  3. import ch.ethz.ssh2.Session;
  4. import com.demo.entity.SSHVo;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.context.annotation.PropertySource;
  7. import org.springframework.stereotype.Component;
  8. import java.io.FileNotFoundException;
  9. import java.io.IOException;
  10. import java.io.InputStream;
  11. import java.nio.charset.Charset;
  12. import java.nio.charset.StandardCharsets;
  13. /**
  14. * @Author: 瞎琢磨先生lyg
  15. * @Date: 2020/4/4 12:10
  16. * @Version 1.0
  17. * Just do it, Just do IT
  18. */
  19. @Component
  20. @PropertySource(value = {"classpath:config/ssh.properties"})
  21. public class SSHTool {
  22. private static Charset charset = StandardCharsets.UTF_8;
  23. private static Connection conn;
  24. private static String ipAddr;
  25. private static Integer port;
  26. private static String userName;
  27. private static String password;
  28. private static String cmds;
  29. @Value("${ipAddr}")
  30. public void setIpAddr(String ipAddr) {
  31. SSHTool.ipAddr = ipAddr;
  32. }
  33. @Value("${port}")
  34. public void setPort(Integer port) {
  35. SSHTool.port = port;
  36. }
  37. @Value("${userName}")
  38. public void setUserName(String userName) {
  39. SSHTool.userName = userName;
  40. }
  41. @Value("${password}")
  42. public void setPassword(String password) {
  43. SSHTool.password = password;
  44. }
  45. @Value("${cmds}")
  46. public void setCmds(String cmds) {
  47. SSHTool.cmds = cmds;
  48. }
  49. /**
  50. * 登录远程Linux主机
  51. *
  52. * @return 是否登录成功
  53. */
  54. private static boolean login(SSHVo sshVo) {
  55. try {
  56. // 连接
  57. //conn = new Connection(ipAddr, port);
  58. conn = new Connection(sshVo.getIpAddr(), sshVo.getPort());
  59. conn.connect();
  60. // 认证
  61. return conn.authenticateWithPassword("root", sshVo.getPassword());
  62. } catch (IOException e) {
  63. e.printStackTrace();
  64. return false;
  65. }
  66. }
  67. /**
  68. * 执行Shell脚本或命令
  69. *
  70. * @param sshVo .cmds 命令行序列
  71. * @return 脚本输出结果
  72. */
  73. public static StringBuilder exec(SSHVo sshVo) throws IOException {
  74. InputStream in = null;
  75. StringBuilder result = new StringBuilder();
  76. if (sshVo == null || (sshVo != null && (sshVo.getIpAddr() == null || "".equals(sshVo.getIpAddr())))) {
  77. sshVo = new SSHVo(ipAddr, port, userName, password, cmds);
  78. }
  79. System.out.println(sshVo
  80. );
  81. try {
  82. if (SSHTool.login(sshVo)) {
  83. // 打开一个会话
  84. Session session = conn.openSession();
  85. session.execCommand(sshVo.getCmds());
  86. in = session.getStdout();
  87. result = SSHTool.processStdout(in, SSHTool.charset);
  88. conn.close();
  89. }
  90. } finally {
  91. if (null != in) {
  92. in.close();
  93. }
  94. }
  95. return result;
  96. }
  97. /**
  98. * 解析流获取字符串信息
  99. *
  100. * @param in 输入流对象
  101. * @param charset 字符集
  102. * @return 脚本输出结果
  103. */
  104. public static StringBuilder processStdout(InputStream in, Charset charset) throws FileNotFoundException {
  105. byte[] buf = new byte[1024];
  106. StringBuilder sb = new StringBuilder();
  107. // OutputStream os = new FileOutputStream("./data.txt");
  108. try {
  109. int length;
  110. while ((length = in.read(buf)) != -1) {
  111. // os.write(buf, 0, c);
  112. sb.append(new String(buf, 0, length));
  113. }
  114. } catch (IOException e) {
  115. e.printStackTrace();
  116. }
  117. return sb;
  118. }
  119. }

ssh.properties

(注意位置是在 resources/config 目录下)

  1. ipAddr=124.160.108.89
  2. port=22
  3. userName=root
  4. password=root
  5. cmds=sh -x /test/shell.sh

测试类

  1. public static void main(String[] args) throws IOException {
  2. SSHTool.exec(new SSHVo(
  3. "124.160.108.88",
  4. 22,
  5. "root",
  6. "root",
  7. "mkdir -p /java/shell"));
  8. }

访问 localhost:8080/shell

Java代码远程调用shell脚本 - 图2

查看服务器,发现服务器上也调用了相应的 shell 脚本,说明 Java 代码调用 shell 脚本执行成功。

[注意事项]

  1. 使用Java代码调用shell脚本杀死当前服务,然后重启。无论尝试多少次,都无法进行重启。而单纯的使用shell实现杀死并重启服务却正常。

原因:Java代码调用shell杀死当前服务后,相当于调用shell脚本的主线程被杀了,子线程调用也就无从说起了。
解决办法:(曲线救国)。使用守护线程启动服务,当Java代码调用shell脚本杀死服务后,守护线程检测到服务被杀死,会自动将服务重启。

  1. 在win系统下编写的shell脚本极有可能无法在linux服务器上正常运行。

原因:就在于编码格式不一致。
解决办法:要么就在 linux 服务器上编写脚本,要么在win电脑上编写完毕后,使用 notepad++ 将其转码成linux的编码格式。
image.png

  1. 使用 shell 脚本调用 MySQL 脚本(eg. user.sql)报错,但是单独将其放在 Navicat 中执行,却一点错误都没有。

原因:SQL 脚本中存在以”#”开头的注释,导致 linux 上无法正常解析。
解决办法:将 SQL 脚本中所有以”#”开头的注释统统替换成”— “的注释(注意:—和待注释的内容之间要用”空格”隔开)。