增加主机信息功能
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package com.jeesite.modules.utils;
|
||||
|
||||
import com.jcraft.jsch.*;
|
||||
import com.jcraft.jsch.ChannelExec;
|
||||
import com.jcraft.jsch.JSch;
|
||||
import com.jcraft.jsch.Session;
|
||||
import com.jeesite.modules.apps.Module.ContainerInfo;
|
||||
import com.jeesite.modules.apps.Module.DockerResult;
|
||||
import com.jeesite.modules.apps.Module.SystemInfo;
|
||||
@@ -14,135 +16,104 @@ import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Docker 工具类(连接池版本)
|
||||
*/
|
||||
public class DockerUtil {
|
||||
|
||||
private static final int SSH_TIMEOUT = 3000;
|
||||
private static final int MAX_POOL_SIZE = 50;
|
||||
private static final int SSH_TIMEOUT = 10000;
|
||||
private static final Map<String, Session> sessionCache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 连接池:accountId -> Session
|
||||
*/
|
||||
private static final Map<String, PooledSession> sessionPool = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 被池化的 Session,包含创建时间和活跃标记
|
||||
*/
|
||||
private static class PooledSession {
|
||||
Session session;
|
||||
long createdAt;
|
||||
volatile boolean inUse; // 防止并发误删
|
||||
|
||||
PooledSession(Session session) {
|
||||
this.session = session;
|
||||
this.createdAt = System.currentTimeMillis();
|
||||
this.inUse = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建 Session(线程安全,复用已有连接)
|
||||
*/
|
||||
private static PooledSession getSession(MySftpAccounts account) throws JSchException {
|
||||
private static Session getSession(MySftpAccounts account) throws Exception {
|
||||
String key = account.getAccountId();
|
||||
PooledSession pooled = sessionPool.get(key);
|
||||
if (pooled != null && !pooled.inUse && pooled.session.isConnected()) {
|
||||
if (System.currentTimeMillis() - pooled.createdAt > 30 * 60 * 1000) {
|
||||
pooled.session.disconnect();
|
||||
pooled.session = null;
|
||||
} else {
|
||||
return pooled;
|
||||
}
|
||||
|
||||
// 1. 从缓存取
|
||||
Session session = sessionCache.get(key);
|
||||
if (session != null && session.isConnected()) {
|
||||
return session;
|
||||
}
|
||||
|
||||
// 2. 超过容量上限,先清理一个过期连接
|
||||
if (sessionPool.size() >= MAX_POOL_SIZE) {
|
||||
sessionPool.entrySet().removeIf(e -> {
|
||||
if (System.currentTimeMillis() - e.getValue().createdAt > 20 * 60 * 1000) {
|
||||
e.getValue().session.disconnect();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
// 2. 无效则移除旧的
|
||||
if (session != null) {
|
||||
session.disconnect();
|
||||
sessionCache.remove(key);
|
||||
}
|
||||
|
||||
// 3. 新建连接
|
||||
// 3. 创建新连接
|
||||
JSch jsch = new JSch();
|
||||
int port = account.getHostPort() == null ? 22 : account.getHostPort();
|
||||
Session session = jsch.getSession(account.getUsername(), account.getHostIp(), port);
|
||||
session = jsch.getSession(account.getUsername(), account.getHostIp(), port);
|
||||
session.setTimeout(SSH_TIMEOUT);
|
||||
|
||||
// 密钥/密码认证
|
||||
if ("key".equalsIgnoreCase(account.getAuthType()) && account.getPrivateKey() != null) {
|
||||
jsch.addIdentity("key_" + key,
|
||||
account.getPrivateKey().getBytes(StandardCharsets.UTF_8), null, null);
|
||||
byte[] prvKey = account.getPrivateKey().getBytes(StandardCharsets.UTF_8);
|
||||
jsch.addIdentity("id_" + key, prvKey, null, null);
|
||||
} else {
|
||||
session.setPassword(account.getPassword());
|
||||
}
|
||||
|
||||
Hashtable<String, String> config = new Hashtable<>();
|
||||
Properties config = new Properties();
|
||||
config.put("StrictHostKeyChecking", "no");
|
||||
session.setConfig(config);
|
||||
session.connect(SSH_TIMEOUT);
|
||||
|
||||
pooled = new PooledSession(session);
|
||||
sessionPool.put(key, pooled);
|
||||
return pooled;
|
||||
sessionCache.put(key, session);
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行单条命令(通过池化 Session)
|
||||
*/
|
||||
// ====================== 执行命令(最稳定)======================
|
||||
private static String runCommand(MySftpAccounts account, String cmd) {
|
||||
PooledSession pooled = null;
|
||||
Session session = null;
|
||||
ChannelExec channel = null;
|
||||
InputStream in = null;
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
pooled = getSession(account);
|
||||
pooled.inUse = true;
|
||||
session = getSession(account);
|
||||
|
||||
// 拼接命令
|
||||
String command = cmd;
|
||||
if (StringUtils.isNotBlank(account.getRootPath())) {
|
||||
command = "cd " + account.getRootPath() + " && " + cmd;
|
||||
}
|
||||
|
||||
Session session = pooled.session;
|
||||
channel = (ChannelExec) session.openChannel("exec");
|
||||
|
||||
String command = account.getRootPath() != null && !account.getRootPath().isEmpty()
|
||||
? "cd " + account.getRootPath() + " && " + cmd
|
||||
: cmd;
|
||||
channel.setCommand(command);
|
||||
InputStream in = channel.getInputStream();
|
||||
channel.setErrStream(System.err, true); // 把错误流也读出来
|
||||
in = channel.getInputStream();
|
||||
channel.connect(SSH_TIMEOUT);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
byte[] buf = new byte[4096];
|
||||
int len;
|
||||
while ((len = in.read(buf)) != -1) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
|
||||
return out.toString(StandardCharsets.UTF_8).trim();
|
||||
|
||||
} catch (Exception e) {
|
||||
if (pooled != null) {
|
||||
sessionPool.remove(account.getAccountId());
|
||||
if (pooled.session.isConnected()) {
|
||||
pooled.session.disconnect();
|
||||
}
|
||||
// 执行失败,清理坏连接
|
||||
if (session != null) {
|
||||
session.disconnect();
|
||||
sessionCache.remove(account.getAccountId());
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
if (channel != null) channel.disconnect();
|
||||
if (pooled != null) pooled.inUse = false;
|
||||
try {
|
||||
if (in != null) in.close();
|
||||
if (channel != null) channel.disconnect();
|
||||
out.close();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出容器(一次 SSH 连接获取所有信息)
|
||||
*/
|
||||
public static List<ContainerInfo> listContainers(MySftpAccounts accounts, boolean all) {
|
||||
String FORMAT = "{{.ID}}|{{.Image}}|{{.Command}}|{{.CreatedAt}}|{{.Status}}|{{.Ports}}|{{.Names}}";
|
||||
String cmd = (all ? "docker ps -a" : "docker ps") + " --format \"" + FORMAT + "\"";
|
||||
String output = runCommand(accounts, cmd);
|
||||
|
||||
if (output == null || output.isBlank()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return Arrays.stream(output.split("\\R"))
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.map(line -> {
|
||||
@@ -163,17 +134,17 @@ public class DockerUtil {
|
||||
|
||||
public static DockerResult start(MySftpAccounts accounts, String containerId) {
|
||||
String res = runCommand(accounts, "docker start " + containerId);
|
||||
return res != null ? DockerResult.ok(res) : DockerResult.fail("执行失败");
|
||||
return res != null ? DockerResult.ok(res) : DockerResult.fail("启动失败");
|
||||
}
|
||||
|
||||
public static DockerResult stop(MySftpAccounts accounts, String containerId) {
|
||||
String res = runCommand(accounts, "docker stop " + containerId);
|
||||
return res != null ? DockerResult.ok(res) : DockerResult.fail("执行失败");
|
||||
return res != null ? DockerResult.ok(res) : DockerResult.fail("停止失败");
|
||||
}
|
||||
|
||||
public static DockerResult restart(MySftpAccounts accounts, String containerId) {
|
||||
String res = runCommand(accounts, "docker restart " + containerId);
|
||||
return res != null ? DockerResult.ok(res) : DockerResult.fail("执行失败");
|
||||
return res != null ? DockerResult.ok(res) : DockerResult.fail("重启失败");
|
||||
}
|
||||
|
||||
public static DockerResult getLogs(MySftpAccounts accounts, String containerId, int tail, boolean timestamps) {
|
||||
@@ -184,64 +155,35 @@ public class DockerUtil {
|
||||
|
||||
public static DockerResult listRaw(MySftpAccounts accounts, boolean all) {
|
||||
String res = runCommand(accounts, all ? "docker ps -a" : "docker ps");
|
||||
return res != null ? DockerResult.ok(res) : DockerResult.fail("获取列表失败");
|
||||
return res != null ? DockerResult.ok(res) : DockerResult.fail("获取容器列表失败");
|
||||
}
|
||||
|
||||
public static DockerResult inspect(MySftpAccounts accounts, String containerId) {
|
||||
String res = runCommand(accounts, "docker inspect " + containerId);
|
||||
return res != null ? DockerResult.ok(res) : DockerResult.fail("查询详情失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统状态:CPU + 内存 + 磁盘,三项一次 SSH 连接搞定
|
||||
*/
|
||||
public static SystemInfo systemInfo(MySftpAccounts accounts) {
|
||||
String cmd =
|
||||
"echo CPU:$(top -bn1 2>/dev/null | grep 'Cpu(s)' | awk '{printf \"%.1f\", 100-$8}') && " +
|
||||
"echo MEM:$(free 2>/dev/null | grep Mem | awk '{printf \"%.1f\", $3/$2*100}') && " +
|
||||
"echo DISK:$(df -h / 2>/dev/null | grep / | awk '{gsub(/%/,\"\"); print $5}')";
|
||||
|
||||
String output = runCommand(accounts, cmd);
|
||||
SystemInfo info = new SystemInfo();
|
||||
if (output == null) {
|
||||
info.setCpu("N/A");
|
||||
info.setMemory("N/A");
|
||||
info.setDisk("N/A");
|
||||
return info;
|
||||
}
|
||||
String output = runCommand(accounts,
|
||||
"top -bn1 | grep 'Cpu(s)' | awk '{printf \"%.1f\", 100-$8}'; echo; " +
|
||||
"free | grep Mem | awk '{printf \"%.1f\", $3/$2*100}'; echo; " +
|
||||
"df -h / | grep / | awk '{gsub(/%/,\"\"); print $5}'"
|
||||
);
|
||||
|
||||
for (String line : output.split("\\R")) {
|
||||
if (line.startsWith("CPU:")) info.setCpu(line.substring(4).trim());
|
||||
else if (line.startsWith("MEM:")) info.setMemory(line.substring(4).trim());
|
||||
else if (line.startsWith("DISK:")) info.setDisk(line.substring(5).trim());
|
||||
}
|
||||
if (output == null) return info;
|
||||
String[] lines = output.split("\\R");
|
||||
if (lines.length >= 1) info.setCpu(lines[0].trim());
|
||||
if (lines.length >= 2) info.setMemory(lines[1].trim());
|
||||
if (lines.length >= 3) info.setDisk(lines[2].trim());
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭指定账号的连接(账号删除或更新密码时调用)
|
||||
*/
|
||||
public static void closeSession(String accountId) {
|
||||
PooledSession pooled = sessionPool.remove(accountId);
|
||||
if (pooled != null && pooled.session.isConnected()) {
|
||||
pooled.session.disconnect();
|
||||
}
|
||||
Session s = sessionCache.remove(accountId);
|
||||
if (s != null && s.isConnected()) s.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有连接池
|
||||
*/
|
||||
public static void closeAll() {
|
||||
sessionPool.values().forEach(p -> {
|
||||
if (p.session.isConnected()) p.session.disconnect();
|
||||
});
|
||||
sessionPool.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前池大小(调试用)
|
||||
*/
|
||||
public static int poolSize() {
|
||||
return sessionPool.size();
|
||||
sessionCache.values().forEach(s -> { if (s.isConnected()) s.disconnect(); });
|
||||
sessionCache.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user