🔨 添加 vnc 会话.

This commit is contained in:
lijiahangmax
2025-07-07 00:04:29 +08:00
parent 4b25de3811
commit aed5d10eed
16 changed files with 311 additions and 93 deletions

View File

@@ -25,6 +25,7 @@ package org.dromara.visor.module.terminal.configuration;
import org.dromara.visor.module.terminal.handler.terminal.TerminalAccessRdpHandler;
import org.dromara.visor.module.terminal.handler.terminal.TerminalAccessSftpHandler;
import org.dromara.visor.module.terminal.handler.terminal.TerminalAccessSshHandler;
import org.dromara.visor.module.terminal.handler.terminal.TerminalAccessVncHandler;
import org.dromara.visor.module.terminal.handler.transfer.TransferMessageDispatcher;
import org.dromara.visor.module.terminal.interceptor.TerminalAccessInterceptor;
import org.dromara.visor.module.terminal.interceptor.TerminalTransferInterceptor;
@@ -63,6 +64,9 @@ public class TerminalWebSocketConfiguration implements WebSocketConfigurer {
@Resource
private TerminalAccessRdpHandler terminalAccessRdpHandler;
@Resource
private TerminalAccessVncHandler terminalAccessVncHandler;
@Resource
private TransferMessageDispatcher transferMessageDispatcher;
@@ -80,6 +84,10 @@ public class TerminalWebSocketConfiguration implements WebSocketConfigurer {
registry.addHandler(terminalAccessRdpHandler, prefix + "/terminal/access/rdp/{accessToken}")
.addInterceptors(terminalAccessInterceptor)
.setAllowedOrigins("*");
// VNC 终端会话
registry.addHandler(terminalAccessVncHandler, prefix + "/terminal/access/vnc/{accessToken}")
.addInterceptors(terminalAccessInterceptor)
.setAllowedOrigins("*");
// 文件传输
registry.addHandler(transferMessageDispatcher, prefix + "/terminal/transfer/{transferToken}")
.addInterceptors(terminalTransferInterceptor)

View File

@@ -192,12 +192,16 @@ public class GuacdTunnel implements IGuacdTunnel {
}
@Override
public void size(int width, int height, int dpi) {
public void size(int width, int height) {
clientConfig.setOptimalScreenWidth(width);
clientConfig.setOptimalScreenHeight(height);
clientConfig.setOptimalResolution(dpi);
this.setParameter(GuacdConst.WIDTH, width);
this.setParameter(GuacdConst.HEIGHT, height);
}
@Override
public void dpi(int dpi) {
clientConfig.setOptimalResolution(dpi);
this.setParameter(GuacdConst.DPI, dpi);
}

View File

@@ -64,9 +64,15 @@ public interface IGuacdTunnel extends Runnable, Executable, SafeCloseable {
*
* @param width width
* @param height height
*/
void size(int width, int height);
/**
* dpi
*
* @param dpi dpi
*/
void size(int width, int height, int dpi);
void dpi(int dpi);
/**
* 设置时区

View File

@@ -392,6 +392,16 @@ public interface GuacdConst {
*/
String CLIPBOARD_ENCODING = "clipboard-encoding";
/**
* 压缩等级
*/
String COMPRESS_LEVEL = "compress-level";
/**
* 质量等级
*/
String QUALITY_LEVEL = "quality-level";
// -------------------- const --------------------
String RESIZE_METHOD_DISPLAY_UPDATE = "display-update";

View File

@@ -0,0 +1,44 @@
package org.dromara.visor.module.terminal.handler.terminal;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.module.terminal.handler.terminal.enums.InputProtocolEnum;
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelProps;
import org.dromara.visor.module.terminal.handler.terminal.sender.IGuacdTerminalSender;
import org.dromara.visor.module.terminal.handler.terminal.sender.WebsocketGuacdTerminalSender;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
/**
* VNC 终端处理器
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/28 14:33
*/
@Slf4j
@Component
public class TerminalAccessVncHandler extends AbstractTerminalAccessHandler<IGuacdTerminalSender> {
@Override
protected IGuacdTerminalSender createSender(WebSocketSession channel) {
return new WebsocketGuacdTerminalSender(channel);
}
@Override
protected void handleMessage(WebSocketSession channel, TextMessage message, TerminalChannelProps props, IGuacdTerminalSender sender) {
String payload = message.getPayload();
try {
// 解析类型
InputProtocolEnum type = InputProtocolEnum.of(payload);
if (type == null) {
return;
}
// 解析并处理消息
type.getHandler().handle(props, sender, type.parse(payload));
} catch (Exception e) {
log.error("TerminalAccessVncHandler-handleMessage-error id: {}, msg: {}", channel.getId(), payload, e);
}
}
}

View File

@@ -26,7 +26,7 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelProps;
import org.dromara.visor.module.terminal.handler.terminal.model.request.GuacdInstructionRequest;
import org.dromara.visor.module.terminal.handler.terminal.sender.IGuacdTerminalSender;
import org.dromara.visor.module.terminal.handler.terminal.session.IRdpSession;
import org.dromara.visor.module.terminal.handler.terminal.session.IGuacdSession;
import org.dromara.visor.module.terminal.handler.terminal.session.ITerminalSession;
import org.springframework.stereotype.Component;
@@ -45,20 +45,10 @@ public class GuacdInstructionHandler extends AbstractTerminalHandler<IGuacdTermi
public void handle(TerminalChannelProps props, IGuacdTerminalSender sender, GuacdInstructionRequest payload) {
// 获取会话
ITerminalSession session = terminalManager.getSession(props.getId());
if (session instanceof IRdpSession) {
// 处理 rdp 指令
this.processRdpInstruction((IRdpSession) session, payload.getInstruction());
// 发送指令
if (session instanceof IGuacdSession) {
((IGuacdSession) session).write(payload.getInstruction());
}
}
/**
* 处理 rdp 指令
*
* @param session session
* @param instruction instruction
*/
private void processRdpInstruction(IRdpSession session, String instruction) {
session.write(instruction);
}
}

View File

@@ -35,6 +35,7 @@ import org.dromara.visor.common.constant.ExtraFieldConst;
import org.dromara.visor.common.session.config.BaseConnectConfig;
import org.dromara.visor.common.session.config.RdpConnectConfig;
import org.dromara.visor.common.session.config.SshConnectConfig;
import org.dromara.visor.common.session.config.VncConnectConfig;
import org.dromara.visor.common.session.ssh.SessionStores;
import org.dromara.visor.framework.biz.operator.log.core.model.OperatorLogModel;
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
@@ -52,10 +53,7 @@ import org.dromara.visor.module.terminal.handler.terminal.constant.SessionCloseC
import org.dromara.visor.module.terminal.handler.terminal.constant.TerminalMessage;
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelExtra;
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelProps;
import org.dromara.visor.module.terminal.handler.terminal.model.config.ITerminalSessionConfig;
import org.dromara.visor.module.terminal.handler.terminal.model.config.TerminalSessionRdpConfig;
import org.dromara.visor.module.terminal.handler.terminal.model.config.TerminalSessionSftpConfig;
import org.dromara.visor.module.terminal.handler.terminal.model.config.TerminalSessionSshConfig;
import org.dromara.visor.module.terminal.handler.terminal.model.config.*;
import org.dromara.visor.module.terminal.handler.terminal.model.request.TerminalConnectRequest;
import org.dromara.visor.module.terminal.handler.terminal.model.transport.TerminalConnectBody;
import org.dromara.visor.module.terminal.handler.terminal.model.transport.TerminalSetInfo;
@@ -63,10 +61,7 @@ import org.dromara.visor.module.terminal.handler.terminal.sender.IGuacdTerminalS
import org.dromara.visor.module.terminal.handler.terminal.sender.ISftpTerminalSender;
import org.dromara.visor.module.terminal.handler.terminal.sender.ISshTerminalSender;
import org.dromara.visor.module.terminal.handler.terminal.sender.ITerminalSender;
import org.dromara.visor.module.terminal.handler.terminal.session.ITerminalSession;
import org.dromara.visor.module.terminal.handler.terminal.session.RdpSession;
import org.dromara.visor.module.terminal.handler.terminal.session.SftpSession;
import org.dromara.visor.module.terminal.handler.terminal.session.SshSession;
import org.dromara.visor.module.terminal.handler.terminal.session.*;
import org.dromara.visor.module.terminal.service.TerminalConnectLogService;
import org.springframework.stereotype.Component;
@@ -238,6 +233,12 @@ public class TerminalConnectHandler extends AbstractTerminalHandler<ITerminalSen
config.setDpi(connectParams.getDpi());
this.setBaseSessionConfig(config, connectLog, connectParams);
session = new RdpSession(props, (IGuacdTerminalSender) sender, config, guacdConfig);
} else if (TerminalConnectTypeEnum.VNC.name().equals(connectType)) {
// 打开 vnc 会话
TerminalSessionVncConfig config = TerminalSessionConvert.MAPPER.toVnc((VncConnectConfig) connectConfig);
config.setDpi(connectParams.getDpi());
this.setBaseSessionConfig(config, connectLog, connectParams);
session = new VncSession(props, (IGuacdTerminalSender) sender, config, guacdConfig);
} else {
throw Exceptions.unsupported();
}

View File

@@ -26,8 +26,7 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelProps;
import org.dromara.visor.module.terminal.handler.terminal.model.request.TerminalResizeRequest;
import org.dromara.visor.module.terminal.handler.terminal.sender.ITerminalSender;
import org.dromara.visor.module.terminal.handler.terminal.session.IRdpSession;
import org.dromara.visor.module.terminal.handler.terminal.session.ISshSession;
import org.dromara.visor.module.terminal.handler.terminal.session.IResizeableSession;
import org.dromara.visor.module.terminal.handler.terminal.session.ITerminalSession;
import org.springframework.stereotype.Component;
@@ -44,16 +43,10 @@ public class TerminalResizeHandler extends AbstractTerminalHandler<ITerminalSend
@Override
public void handle(TerminalChannelProps props, ITerminalSender sender, TerminalResizeRequest payload) {
Integer width = payload.getWidth();
Integer height = payload.getHeight();
// 获取会话
ITerminalSession session = terminalManager.getSession(props.getId());
if (session instanceof ISshSession) {
// SSH
((ISshSession) session).resize(width, height);
} else if (session instanceof IRdpSession) {
// RDP
((IRdpSession) session).resize(width, height);
if (session instanceof IResizeableSession) {
((IResizeableSession) session).resize(payload.getWidth(), payload.getHeight());
}
}

View File

@@ -23,9 +23,11 @@
package org.dromara.visor.module.terminal.handler.terminal.session;
import cn.orionsec.kit.lang.utils.Exceptions;
import org.dromara.visor.common.utils.AesEncryptUtils;
import org.dromara.visor.module.terminal.define.TerminalThreadPools;
import org.dromara.visor.module.terminal.handler.guacd.IGuacdTunnel;
import org.dromara.visor.module.terminal.handler.terminal.constant.TerminalMessage;
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelExtra;
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelProps;
import org.dromara.visor.module.terminal.handler.terminal.model.config.ITerminalSessionConfig;
import org.dromara.visor.module.terminal.handler.terminal.sender.IGuacdTerminalSender;
@@ -70,10 +72,51 @@ public abstract class AbstractGuacdSession<C extends ITerminalSessionConfig>
*/
protected abstract IGuacdTunnel createTunnel();
/**
* 是否为低带宽模式
*
* @return is
*/
protected abstract boolean isLowBandwidthMode();
/**
* 设置 tunnel 参数
*/
protected abstract void setTunnelParams();
protected void setTunnelParams() {
// 设置低带宽模式
if (this.isLowBandwidthMode()) {
this.setLowBandwidthMode();
}
// 主机信息
tunnel.remote(config.getHostAddress(), config.getHostPort());
// 身份信息
tunnel.auth(config.getUsername(), AesEncryptUtils.decryptAsString(config.getPassword()));
// 大小
tunnel.size(config.getWidth(), config.getHeight());
}
/**
* 设置低带宽模式
*/
protected void setLowBandwidthMode() {
TerminalChannelExtra extra = props.getExtra();
extra.setColorDepth(8);
extra.setForceLossless(false);
extra.setEnableWallpaper(false);
extra.setEnableTheming(false);
extra.setEnableFontSmoothing(false);
extra.setEnableFullWindowDrag(false);
extra.setEnableDesktopComposition(false);
extra.setEnableMenuAnimations(false);
extra.setDisableBitmapCaching(false);
extra.setDisableOffscreenCaching(false);
extra.setDisableGlyphCaching(false);
extra.setDisableGfx(false);
extra.setEnableAudioInput(false);
extra.setEnableAudioOutput(false);
extra.setCompressLevel(9);
extra.setQualityLevel(1);
}
/**
* 执行连接
@@ -92,6 +135,13 @@ public abstract class AbstractGuacdSession<C extends ITerminalSessionConfig>
tunnel.write(data);
}
@Override
public void resize(int width, int height) {
config.setWidth(width);
config.setHeight(height);
tunnel.writeInstruction("size", String.valueOf(width), String.valueOf(height));
}
@Override
public void keepAlive() {
// guacd 有内部实现

View File

@@ -29,7 +29,7 @@ package org.dromara.visor.module.terminal.handler.terminal.session;
* @version 1.0.0
* @since 2025/3/30 17:42
*/
public interface IGuacdSession extends ITerminalSession {
public interface IGuacdSession extends ITerminalSession, IResizeableSession {
/**
* 写入数据

View File

@@ -30,13 +30,4 @@ package org.dromara.visor.module.terminal.handler.terminal.session;
* @since 2025/3/30 17:42
*/
public interface IRdpSession extends IGuacdSession {
/**
* 重置大小
*
* @param width width
* @param height height
*/
void resize(int width, int height);
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.terminal.handler.terminal.session;
/**
* 可修改大小的会话
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/7/3 2:16
*/
public interface IResizeableSession {
/**
* 修改大小
*
* @param width width
* @param height height
*/
void resize(int width, int height);
}

View File

@@ -32,15 +32,7 @@ import org.dromara.visor.module.terminal.handler.terminal.sender.ISshTerminalSen
* @version 1.0.0
* @since 2024/2/4 16:47
*/
public interface ISshSession extends ITerminalSession {
/**
* 重置大小
*
* @param width width
* @param height height
*/
void resize(int width, int height);
public interface ISshSession extends ITerminalSession, IResizeableSession {
/**
* 写入内容

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.terminal.handler.terminal.session;
/**
* vnc 会话
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/7/3 2:04
*/
public interface IVncSession extends IGuacdSession {
}

View File

@@ -27,7 +27,6 @@ import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.io.Files1;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.AppConst;
import org.dromara.visor.common.utils.AesEncryptUtils;
import org.dromara.visor.module.common.config.GuacdConfig;
import org.dromara.visor.module.terminal.enums.DriveMountModeEnum;
import org.dromara.visor.module.terminal.handler.guacd.GuacdTunnel;
@@ -66,19 +65,13 @@ public class RdpSession extends AbstractGuacdSession<TerminalSessionRdpConfig> i
@Override
protected void setTunnelParams() {
super.setTunnelParams();
// 设置额外参数
TerminalChannelExtra extra = props.getExtra();
// 音频输入会导致无法连接先写死
extra.setEnableAudioInput(false);
// 设置低带宽模式
if (Booleans.isTrue(config.getLowBandwidthMode())) {
this.setLowBandwidthMode(extra);
}
// 主机信息
tunnel.remote(config.getHostAddress(), config.getHostPort());
// 身份信息
tunnel.auth(config.getUsername(), AesEncryptUtils.decryptAsString(config.getPassword()));
// 大小
tunnel.size(config.getWidth(), config.getHeight(), config.getDpi());
// dpi
tunnel.dpi(config.getDpi());
// 时区
tunnel.timezone(config.getTimezone());
// 忽略证书
@@ -140,32 +133,8 @@ public class RdpSession extends AbstractGuacdSession<TerminalSessionRdpConfig> i
}
@Override
public void resize(int width, int height) {
config.setWidth(width);
config.setHeight(height);
tunnel.writeInstruction("size", String.valueOf(width), String.valueOf(height));
}
/**
* 低带宽模式
*
* @param extra extra
*/
private void setLowBandwidthMode(TerminalChannelExtra extra) {
extra.setColorDepth(8);
extra.setForceLossless(false);
extra.setEnableWallpaper(false);
extra.setEnableTheming(false);
extra.setEnableFontSmoothing(false);
extra.setEnableFullWindowDrag(false);
extra.setEnableDesktopComposition(false);
extra.setEnableMenuAnimations(false);
extra.setDisableBitmapCaching(false);
extra.setDisableOffscreenCaching(false);
extra.setDisableGlyphCaching(false);
extra.setDisableGfx(false);
extra.setEnableAudioInput(false);
extra.setEnableAudioOutput(false);
protected boolean isLowBandwidthMode() {
return Booleans.isTrue(config.getLowBandwidthMode());
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.terminal.handler.terminal.session;
import cn.orionsec.kit.lang.utils.Booleans;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.module.common.config.GuacdConfig;
import org.dromara.visor.module.terminal.handler.guacd.GuacdTunnel;
import org.dromara.visor.module.terminal.handler.guacd.IGuacdTunnel;
import org.dromara.visor.module.terminal.handler.guacd.constant.GuacdConst;
import org.dromara.visor.module.terminal.handler.guacd.constant.GuacdProtocol;
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelExtra;
import org.dromara.visor.module.terminal.handler.terminal.model.TerminalChannelProps;
import org.dromara.visor.module.terminal.handler.terminal.model.config.TerminalSessionVncConfig;
import org.dromara.visor.module.terminal.handler.terminal.sender.IGuacdTerminalSender;
/**
* vnc 会话
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/3/30 17:44
*/
@Slf4j
public class VncSession extends AbstractGuacdSession<TerminalSessionVncConfig> implements IVncSession {
private final GuacdConfig guacdConfig;
public VncSession(TerminalChannelProps props,
IGuacdTerminalSender sender,
TerminalSessionVncConfig config,
GuacdConfig guacdConfig) {
super(props, sender, config);
this.guacdConfig = guacdConfig;
}
@Override
protected IGuacdTunnel createTunnel() {
return new GuacdTunnel(GuacdProtocol.VNC, sessionId, guacdConfig.getHost(), guacdConfig.getPort());
}
@Override
protected void setTunnelParams() {
super.setTunnelParams();
// 设置额外参数
TerminalChannelExtra extra = props.getExtra();
// 时区
tunnel.timezone(config.getTimezone());
// 显示设置
tunnel.setParameter(GuacdConst.COLOR_DEPTH, extra.getColorDepth());
tunnel.setParameter(GuacdConst.FORCE_LOSSLESS, extra.getForceLossless());
tunnel.setParameter(GuacdConst.COMPRESS_LEVEL, extra.getCompressLevel());
tunnel.setParameter(GuacdConst.QUALITY_LEVEL, extra.getQualityLevel());
// 光标设置
tunnel.setParameter(GuacdConst.CURSOR, extra.getCursor());
// 编码设置
tunnel.setParameter(GuacdConst.CLIPBOARD_ENCODING, config.getClipboardEncoding());
}
@Override
protected boolean isLowBandwidthMode() {
return Booleans.isTrue(config.getLowBandwidthMode());
}
}