@@ -5,90 +5,160 @@ import com.jeesite.modules.apps.Module.ContainerInfo;
import com.jeesite.modules.apps.Module.DockerResult ;
import com.jeesite.modules.apps.Module.SystemInfo ;
import com.jeesite.modules.biz.entity.MySftpAccounts ;
import io.micrometer.common.util.StringUtils ;
import java.io.ByteArrayOutputStream ;
import java.io.InputStream ;
import java.nio.charset.StandardCharsets ;
import java.util.ArrayList ;
import java.util.Hashtable ;
import java.util.Li st ;
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 String runCommand ( MySftpAccounts account , String cmd ) {
JSch jsch = new JSch ( ) ;
Session session = null ;
ChannelExec channel = null ;
/**
* 连接池: accountId -> Session
*/
private static final Map < String , PooledSession > sessionPool = new ConcurrentHashMap < > ( ) ;
try {
int port = account . getHostPort ( ) = = null ? 22 : account . getHostPort ( ) ;
session = jsch . getSession ( account . getUsername ( ) , account . getHostIp ( ) , port ) ;
session . setTimeout ( SSH_TIMEOUT ) ;
/**
* 被池化的 Session, 包含创建时间和活跃标记
*/
private static class PooledSession {
Session session ;
long createdAt ;
volatile boolean inUse ; // 防止并发误删
// 认证
if ( " key " . equalsIgnoreCase ( account . getAuthType ( ) ) & & account . getPrivateKey ( ) ! = null ) {
jsch . addIdentity ( " temp " , account . getPrivateKey ( ) . getBytes ( StandardCharsets . UTF_8 ) , null , null ) ;
PooledSession ( Session session ) {
this . session = session ;
this . createdAt = System . currentTimeMillis ( ) ;
this . inUse = false ;
}
}
/**
* 获取或创建 Session( 线程安全, 复用已有连接)
*/
private static PooledSession getSession ( MySftpAccounts account ) throws JSchException {
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 {
session . setPassword ( account . getPassword ( ) ) ;
return pooled ;
}
}
Hashtable < String , String > config = new Hashtable < > ( ) ;
config . put ( " StrictHostKeyChecking " , " no " ) ;
session . setConfig ( config ) ;
session . connect ( SSH_TIMEOUT ) ;
// 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 ;
} ) ;
}
// 执行命令
// 3. 新建连接
JSch jsch = new JSch ( ) ;
int port = account . getHostPort ( ) = = null ? 22 : account . getHostPort ( ) ;
Session 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 ) ;
} else {
session . setPassword ( account . getPassword ( ) ) ;
}
Hashtable < String , String > config = new Hashtable < > ( ) ;
config . put ( " StrictHostKeyChecking " , " no " ) ;
session . setConfig ( config ) ;
session . connect ( SSH_TIMEOUT ) ;
pooled = new PooledSession ( session ) ;
sessionPool . put ( key , pooled ) ;
return pooled ;
}
/**
* 执行单条命令(通过池化 Session)
*/
private static String runCommand ( MySftpAccounts account , String cmd ) {
PooledSession pooled = null ;
ChannelExec channel = null ;
try {
pooled = getSession ( account ) ;
pooled . inUse = true ;
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 . connect ( ) ;
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 ( ) ;
}
}
return null ;
} finally {
if ( channel ! = null ) channel . disconnect ( ) ;
if ( session ! = null ) session . disconnect ( ) ;
if ( pooled ! = null ) pooled . inUse = false ;
}
}
/**
* 列出容器(一次 SSH 连接获取所有信息)
*/
public static List < ContainerInfo > listContainers ( MySftpAccounts accounts , boolean all ) {
List < ContainerInfo > list = new ArrayList < > ( ) ;
String cmd = ( all ? " docker ps -a " : " docker ps " )
+ " --format \" {{.ID}}|{{.Image}}|{{.Command}}|{{.CreatedAt}}|{{.Status}}|{{.Ports}}|{{.Names}} \" " ;
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 list ;
for ( String line : output . split ( " \\ R " ) ) {
if ( line . isBlank ( ) ) continue ;
String [ ] arr = line . split ( " \\ | " ) ;
ContainerInfo info = new ContainerInfo ( ) ;
info . setContainerId ( arr . length > 0 ? arr [ 0 ] . trim ( ) : " " ) ;
info . setImage ( arr . length > 1 ? arr [ 1 ] . trim ( ) : " " ) ;
info . setCommand ( arr . length > 2 ? arr [ 2 ] . trim ( ) : " " ) ;
info . setCreated ( arr . length > 3 ? arr [ 3 ] . trim ( ) : " " ) ;
info . setStatus ( arr . length > 4 ? arr [ 4 ] . trim ( ) : " " ) ;
info . setPorts ( arr . length > 5 ? arr [ 5 ] . trim ( ) : " " ) ;
info . setNames ( arr . length > 6 ? arr [ 6 ] . trim ( ) : " " ) ;
info . setAccountId ( accounts . getAccountId ( ) ) ;
list . add ( info ) ;
if ( output = = null | | output . isBlank ( ) ) {
return Collections . emptyList ( ) ;
}
return lis t ;
return Arrays . stream ( output . sp lit( " \\ R " ) )
. filter ( StringUtils : : isNotBlank )
. map ( line - > {
String [ ] arr = line . split ( " \\ | " , 8 ) ;
return new ContainerInfo (
arr . length > 0 ? arr [ 0 ] . trim ( ) : " " ,
arr . length > 1 ? arr [ 1 ] . trim ( ) : " " ,
arr . length > 2 ? arr [ 2 ] . trim ( ) : " " ,
arr . length > 3 ? arr [ 3 ] . trim ( ) : " " ,
arr . length > 4 ? arr [ 4 ] . trim ( ) : " " ,
arr . length > 5 ? arr [ 5 ] . trim ( ) : " " ,
arr . length > 6 ? arr [ 6 ] . trim ( ) : " " ,
accounts . getAccountId ( )
) ;
} )
. collect ( Collectors . toList ( ) ) ;
}
public static DockerResult start ( MySftpAccounts accounts , String containerId ) {
@@ -112,7 +182,7 @@ public class DockerUtil {
return res ! = null ? DockerResult . ok ( res ) : DockerResult . fail ( " 获取日志失败 " ) ;
}
public static DockerResult list ( MySftpAccounts accounts , boolean all ) {
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 ( " 获取列表失败 " ) ;
}
@@ -122,30 +192,56 @@ public class DockerUtil {
return res ! = null ? DockerResult . ok ( res ) : DockerResult . fail ( " 查询详情失败 " ) ;
}
// 获取 CPU 使用率
public static String getCpuUsage ( MySftpAccounts accounts ) {
// 1秒采样, 输出纯数字百分比
return runCommand ( accounts ,
" top -bn1 | grep 'Cpu(s)' | sed -n '1p' | awk '{printf \" %.1f \" , 100 - $8}' " ) ;
}
// 获取内存使用率
public static String getMemoryUsage ( MySftpAccounts accounts ) {
return runCommand ( accounts ,
" free | grep Mem | awk '{printf \" %.1f \" , $3/$2*100}' " ) ;
}
// 获取磁盘使用率
public static String getDiskUsage ( MySftpAccounts accounts ) {
return runCommand ( accounts ,
" df -h / | grep / | awk '{gsub(/%/, \" \" ); print $5}' " ) ;
}
/**
* 系统状态: CPU + 内存 + 磁盘,三项一次 SSH 连接搞定
*/
public static SystemInfo systemInfo ( MySftpAccounts accounts ) {
SystemInfo systemInfo = new SystemInfo ( ) ;
systemInfo . setCpu ( getCpuUsage ( accounts ) ) ;
systemInfo . setMemory ( getMemoryUsage ( accounts ) ) ;
systemInfo . setDisk ( getDiskUsage ( accounts ) ) ;
return systemInfo ;
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 ;
}
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 ( ) ) ;
}
return info ;
}
}
/**
* 关闭指定账号的连接(账号删除或更新密码时调用)
*/
public static void closeSession ( String accountId ) {
PooledSession pooled = sessionPool . remove ( accountId ) ;
if ( pooled ! = null & & pooled . session . isConnected ( ) ) {
pooled . session . 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 ( ) ;
}
}