Compare commits

..

1 Commits

Author SHA1 Message Date
weishao 093eec8f22 去除swagger 2023-06-09 17:52:26 +08:00
23 changed files with 189 additions and 982 deletions

70
pom.xml
View File

@ -15,7 +15,6 @@
<description>browser(websocket) -> webssh(ssh) -> pod</description>
<properties>
<swagger2.version>2.6.1</swagger2.version>
<java.version>1.8</java.version>
<jsch.version>0.1.54</jsch.version>
<fastjson.version>1.2.20</fastjson.version>
@ -26,17 +25,6 @@
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<artifactId>log4j-api</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- redis依赖包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
@ -62,19 +50,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger2.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger2.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.jcraft/jsch -->
<dependency>
<groupId>com.jcraft</groupId>
@ -100,51 +75,6 @@
<version>${commons-io.version}</version>
</dependency>
<!-- Main Guacamole library -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-common</artifactId>
<version>1.4.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>

View File

@ -1,11 +1,8 @@
package net.educoder.bridge;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("net.educoder.bridge.dao")
@SpringBootApplication()
public class WebsshApplication {
@ -13,6 +10,4 @@ public class WebsshApplication {
SpringApplication.run(WebsshApplication.class, args);
}
}

View File

@ -1,62 +0,0 @@
package net.educoder.bridge.common.model;
import lombok.Data;
import java.time.LocalDateTime;
/**
* windows实例信息
*/
@Data
public class WindowsInfo {
private Long id;
private String uniqId;
private String instanceId;
private String userID;
private String templateName;
private String port;
private String vncPort;
private String forwardTableId;
private String forwardEntryId;
private String vncForwardEntryId;
private LocalDateTime autoReleaseTime;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Integer status;
public WindowsInfo() {
}
public WindowsInfo(String uniqId, String instanceId, String userID, String templateName, String port, String vncPort,
LocalDateTime autoReleaseTime, String forwardTableId, String forwardEntryId, String vncForwardEntryId) {
this.uniqId = uniqId;
this.instanceId = instanceId;
this.userID = userID;
this.templateName = templateName;
this.port = port;
this.vncPort = vncPort;
this.autoReleaseTime = autoReleaseTime;
this.forwardTableId = forwardTableId;
this.forwardEntryId = forwardEntryId;
this.vncForwardEntryId = vncForwardEntryId;
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
this.status = 0;
}
}

View File

@ -1,34 +0,0 @@
package net.educoder.bridge.common.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
/**
* 文件工具类
*
* @author 威少
*/
public class FileUtil {
private static Logger logger = LoggerFactory.getLogger(FileUtil.class);
/**
* 采用Files的delete方法不直接使用file.delete是因为file.delete在删除失败的场景下只会返回false没有具体的错误原因返回
*/
public static String delete(File file) {
String path = file.getPath();
try {
Files.delete(file.toPath());
logger.info("删除文件成功file: {}", path);
} catch (NoSuchFileException e) {
logger.info("删除文件失败file: {},文件不存在", path);
} catch (IOException e) {
logger.warn("删除文件失败file: {}e: {}", path, e.getMessage());
}
return path;
}
}

View File

@ -1,19 +0,0 @@
package net.educoder.bridge.common.utils;
/**
* 线程工具类
*
* @author 威少
*/
public class ThreadUtil {
/**
* 静默睡眠
* @param millis 毫秒
*/
public static void sleepSilently(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ignore) {
}
}
}

View File

@ -1,25 +0,0 @@
package net.educoder.bridge.dao;
import net.educoder.bridge.common.model.WindowsInfo;
import java.util.List;
public interface WindowsInfoMapper {
int deleteByUniqId(String uniqId);
int insertSelective(WindowsInfo record);
WindowsInfo selectByUniqId(String uniqId);
List<WindowsInfo> selectByTpiId(String tpiId);
List<WindowsInfo> selectByUserId(String userID);
int updateByUniqIdSelective(WindowsInfo windowsInfo);
List<String> selectUniqIdByAutoReleaseTime(WindowsInfo windowsInfo);
List<WindowsInfo> selectNotForwardEntryHost();
}

View File

@ -16,8 +16,6 @@ import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import net.educoder.bridge.common.constant.ApiResultCsts;
import net.educoder.bridge.common.model.ApiResult;
import net.educoder.bridge.common.utils.Base64Util;
@ -132,7 +130,7 @@ public class GitGontroller {
* 删除私密版本库
*/
@RequestMapping(path = "/deleteSecret", method = { RequestMethod.POST })
public ApiResult<?> deleteSecret(@ApiParam(name = "secretRepospace", required = true, value = "私密版本库路径") @RequestParam String secretRepospace) {
public ApiResult<?> deleteSecret(@RequestParam String secretRepospace) {
logger.info("[start]deleteSecret: secretRepospace: {}", secretRepospace);
ApiResult<?> result = new ApiResult<>();
@ -146,7 +144,6 @@ public class GitGontroller {
* tpm版本库已更新同步
*/
@RequestMapping(path = "/resetTpmRepository", method = RequestMethod.POST)
@ApiOperation(value = "tpm版本库已更新同步操作", httpMethod = "POST", produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ApiResult<?> reset(@RequestBody GitResetRequestParam param) {
logger.info("[start]tpm版本库已更新同步到tpi版本库 {}", param);
@ -179,7 +176,7 @@ public class GitGontroller {
* 删除私密版本库
*/
@RequestMapping(path = "/clearWorkspace", method = { RequestMethod.POST })
public ApiResult<?> clearWorkspace(@ApiParam(name = "tpiWorkspace", required = true, value = "实训工作空间") @RequestParam String tpiWorkspace) {
public ApiResult<?> clearWorkspace(@RequestParam String tpiWorkspace) {
logger.info("[start]clearWorkspace: tpiWorkspace: {}", tpiWorkspace);
ApiResult<?> result = new ApiResult<>();
@ -237,9 +234,9 @@ public class GitGontroller {
* 导入jupyter tpm文件
*/
@RequestMapping(path = "/updateJupyterTpm", method = { RequestMethod.POST })
public ApiResult<?> updateJupyterTpm(@ApiParam(name = "tpiID", required = true, value = "tpiID") @RequestParam String tpiID,
@ApiParam(name = "content", required = true, value = "文件内容") @RequestParam String content,
@ApiParam(name = "tpiWorkspace", required = true, value = "工作空间") @RequestParam String tpiWorkspace) {
public ApiResult<?> updateJupyterTpm(@RequestParam String tpiID,
@RequestParam String content,
@RequestParam String tpiWorkspace) {
logger.info("[start]updateJupyterTpm tpiID: {}, content: {}, tpiWorkspace", tpiID, content, tpiWorkspace);
ApiResult<?> result = new ApiResult<>();
gitService.updateJupyterTpm(tpiID, content, tpiWorkspace);

View File

@ -1,42 +1,29 @@
package net.educoder.bridge.git.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* git信息
*/
@ApiModel(value = "gitPullRequestParam", description = "git pull接口传参")
public class GitPullRequestParam {
@ApiModelProperty(name = "tpiId", required = true, value = "实训实例的ID")
private String tpiId;
@ApiModelProperty(name = "tpiGitURL", required = true, value = "git项目地址")
private String tpiGitURL;
@ApiModelProperty(name = "tpiWorkspace", required = true, value = "实训工作空间")
private String tpiWorkspace;
@ApiModelProperty(name = "tpiRepoName", required = true, value = "实训项目名")
private String tpiRepoName;
@ApiModelProperty(name = "tpiProtectspace", required = true, value = "实训保护空间")
private String tpiProtectspace;
@ApiModelProperty(name = "tpmScript", required = false, value = "实训评测脚本")
private String tpmScript;
@ApiModelProperty(name = "secretDir", required = false, value = "私密版本库路径")
private String secretDir;
@ApiModelProperty(name = "secretGitUrl", required = false, value = "私密版本库项目地址")
private String secretGitUrl;
@ApiModelProperty(name = "contentModified", required = false, value = "文件是否修改的标志")
private Integer contentModified;
@ApiModelProperty(name = "file", required = false, value = "需要传文件的实训,给出文件存放路径(一个目录)及文件类型")
private String file;
public String getTpiId() {

View File

@ -1,30 +1,21 @@
package net.educoder.bridge.git.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* git信息
*/
@ApiModel(value = "gitResetRequestParam", description = "git reset接口传参")
public class GitResetRequestParam {
@ApiModelProperty(name = "tpiId", required = true, value = "实训实例的ID")
private String tpiId;
@ApiModelProperty(name = "tpiGitURL", required = true, value = "tpi git项目地址")
private String tpiGitURL;
@ApiModelProperty(name = "tpmGitURL", required = true, value = "tpm git项目地址")
private String tpmGitURL;
@ApiModelProperty(name = "tpiWorkspace", required = true, value = "实训工作空间")
private String tpiWorkspace;
@ApiModelProperty(name = "tpiRepoName", required = true, value = "实训项目名")
private String tpiRepoName;
@ApiModelProperty(name = "tpiRepoPath", required = true, value = "实训项目名")
private String tpiRepoPath;
public String getTpiId() {

View File

@ -1,27 +1,18 @@
package net.educoder.bridge.git.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* git信息
*/
@ApiModel(value = "ojRequestParam", description = "oj接口传参")
public class OjRequestParam {
@ApiModelProperty(name = "tpiID", required = true, value = "实训实例的ID")
private String tpiID;
@ApiModelProperty(name = "codeFileName", required = false, value = "代码文件名")
private String codeFileName;
@ApiModelProperty(name = "codeFileContent", required = false, value = "代码文件内容")
private String codeFileContent;
@ApiModelProperty(name = "tpiWorkspace", required = false, value = "工作空间")
private String tpiWorkspace;
@ApiModelProperty(name = "codeFilePath", required = true, value = "代码文件路径")
private String codeFilePath;
public String getTpiID() {

View File

@ -0,0 +1,27 @@
package net.educoder.bridge.git.task;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import net.educoder.bridge.git.model.GitConfig;
import net.educoder.bridge.git.service.GitService;
/**
* git初始化凭证设置
*/
@Component
public class GitInitTask {
@Autowired
private GitConfig gitConfig;
@Autowired
private GitService gitService;
@PostConstruct
public void init() {
gitService.gitCredentialStore(gitConfig);
}
}

View File

@ -0,0 +1,46 @@
package net.educoder.bridge.git.task;
import net.educoder.bridge.git.service.ResourceFileService;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
* execeva.sh初始化
*/
@Component
public class ScriptInitTask {
private final Logger logger = LoggerFactory.getLogger(getClass());
private static final String POD_PLATFORM_MOUNT_PATH = "/data/workspace/platform/eva";
@Autowired
private ResourceFileService readResourceFileContent;
@PostConstruct
public void init() {
writeScript("execEva.sh");
writeScript("execStepOut.sh");
}
private void writeScript(String scriptName) {
String path = POD_PLATFORM_MOUNT_PATH + File.separator + scriptName;
try {
String content = readResourceFileContent.getResourceFileContent("evaluate/" + scriptName);
FileUtils.writeStringToFile(new File(path), content, "UTF-8");
} catch (IOException e) {
logger.error("{} 初始化失败", scriptName, e);
throw new RuntimeException(scriptName + "初始化失败");
}
}
}

View File

@ -1,37 +1,24 @@
package net.educoder.bridge.webssh.config;
import net.educoder.bridge.webssh.handler.GuacamoleWebSocketTunnelHandler;
import net.educoder.bridge.webssh.handler.RunOnlyHandler;
import net.educoder.bridge.webssh.handler.EducoderGuacamoleWebSocketTunnelHandler;
import net.educoder.bridge.webssh.handler.WebsshHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import javax.annotation.Resource;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Resource
@Autowired
WebsshHandler websshHandler;
@Resource
RunOnlyHandler runOnlyHandler;
@Resource
EducoderGuacamoleWebSocketTunnelHandler EducoderGuacamoleWebSocketTunnelHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(websshHandler, "/ws").setAllowedOrigins("*");
registry.addHandler(runOnlyHandler, "/log").setAllowedOrigins("*");
registry.addHandler(EducoderGuacamoleWebSocketTunnelHandler,"/tunnel").setAllowedOrigins("*");
}
}

View File

@ -1,120 +0,0 @@
package net.educoder.bridge.webssh.handler;
import net.educoder.bridge.common.model.WindowsInfo;
import net.educoder.bridge.dao.WindowsInfoMapper;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.GuacamoleSocket;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.InetGuacamoleSocket;
import org.apache.guacamole.net.SimpleGuacamoleTunnel;
import org.apache.guacamole.protocol.ConfiguredGuacamoleSocket;
import org.apache.guacamole.protocol.GuacamoleConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketSession;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
/**
* rdp协议连接
* @author 悟空
*/
@Component
public class EducoderGuacamoleWebSocketTunnelHandler extends GuacamoleWebSocketTunnelHandler {
@Resource
private WindowsInfoMapper windowsInfoMapper;
private static final String DEFAULT_POSITION = "1";
@Value("${guacamole.server.ip}")
private String guacamoleServer;
@Value("${guacamole.server.port}")
private int guacamoleServerPort;
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(EducoderGuacamoleWebSocketTunnelHandler.class);
/**
* 通过tpiId及position生成uniqId默认postion为1
*/
public String generateUniqId(String tpiId) {
return generateUniqId(tpiId, DEFAULT_POSITION);
}
public String generateUniqId(String tpiId, String position) {
return tpiId + "-" + NumberUtils.toInt(position, 1);
}
public String generateUniqId(String envId, String tpiId, String tpiType) {
return tpiId + "-" + envId + "-" + tpiType;
}
@Override
GuacamoleTunnel createTunnel(WebSocketSession session) throws GuacamoleException {
Properties properties = new Properties();
try {
properties.load(new ByteArrayInputStream(session.getUri().getQuery().replace('&','\n').getBytes()));
} catch (IOException e) {
e.printStackTrace();
}
String tpiID = properties.getProperty("tpiID");
String envId = properties.getProperty("envId");
String tpiType = properties.getProperty("tpiType");
//生成唯一键
String uniqId = generateUniqId(envId, tpiID, tpiType);
WindowsInfo windowsInfo = getWindowsInfo(uniqId);
// Create our configuration
GuacamoleConfiguration config = new GuacamoleConfiguration();
config.setProtocol("rdp");
config.setParameter("hostname", "39.105.62.120");
config.setParameter("port", windowsInfo.getVncPort());
config.setParameter("username", "Administrator");
config.setParameter("password", "Edu_123123");
config.setParameter("security", "nla");
config.setParameter("ignore-cert", "true");
config.setParameter("width",properties.getProperty("width"));
config.setParameter("height",properties.getProperty("height"));
// Connect to guacd - everything is hard-coded here.
GuacamoleSocket socket = null;
try{
socket = new ConfiguredGuacamoleSocket(new InetGuacamoleSocket(guacamoleServer, guacamoleServerPort),
config);
}catch (Exception e){
logger.error("createTunnel is Exception uniqueId:{}", uniqId, e);
}
// Return a new tunnel which uses the connected socket
return new SimpleGuacamoleTunnel(socket);
}
public WindowsInfo getWindowsInfo(String uniqId) {
return windowsInfoMapper.selectByUniqId(uniqId);
}
@Override
public List<String> getSubProtocols() {
return Collections.singletonList("guacamole");
}
}

View File

@ -1,200 +0,0 @@
package net.educoder.bridge.webssh.handler;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleConnectionClosedException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.protocol.GuacamoleStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.*;
import java.util.concurrent.atomic.AtomicBoolean;
public abstract class GuacamoleWebSocketTunnelHandler implements WebSocketHandler, SubProtocolCapable {
/**
* The default, minimum buffer size for instructions.
*/
private static final int BUFFER_SIZE = 8192;
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelHandler.class);
/**
* The underlying GuacamoleTunnel. WebSocket reads/writes will be handled as
* reads/writes to this tunnel.
*/
private GuacamoleTunnel tunnel;
/**
* Returns a new tunnel for the given session. How this tunnel is created or
* retrieved is implementation-dependent.
*
* @param session
* The session associated with the active WebSocket connection.
* @return A connected tunnel, or null if no such tunnel exists.
* @throws GuacamoleException
* If an error occurs while retrieving the tunnel, or if access
* to the tunnel is denied.
*/
abstract GuacamoleTunnel createTunnel(WebSocketSession session) throws GuacamoleException;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.debug("afterConnectionEstablished");
try {
// Get tunnel
tunnel = createTunnel(session);
if (tunnel == null) {
String message = Integer.toString(GuacamoleStatus.RESOURCE_NOT_FOUND.getGuacamoleStatusCode());
afterConnectionClosed(session,
new CloseStatus(GuacamoleStatus.RESOURCE_NOT_FOUND.getWebSocketCode(), message));
return;
}
} catch (GuacamoleException e) {
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
logger.debug("Error connecting WebSocket tunnel.", e);
String message = Integer.toString(e.getStatus().getGuacamoleStatusCode());
afterConnectionClosed(session, new CloseStatus(e.getStatus().getWebSocketCode(), message));
return;
}
Thread readThread = new Thread() {
AtomicBoolean flag = new AtomicBoolean(true);
@Override
public void run() {
process(session, flag);
}
};
readThread.start();
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
logger.debug("handleMessage");
// Ignore inbound messages if there is no associated tunnel
if (tunnel == null) {
return;
}
GuacamoleWriter writer = tunnel.acquireWriter();
try {
// Write received message
writer.write(message.getPayload().toString().toCharArray());
} catch (GuacamoleConnectionClosedException e) {
logger.debug("Connection to guacd closed.", e);
} catch (GuacamoleException e) {
logger.debug("WebSocket tunnel write failed.", e);
}
tunnel.releaseWriter();
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
logger.debug("handleTransportError");
}
/**
* Sends the given status on the given WebSocket connection and closes the
* connection.
*
* @param session
* The outbound WebSocket connection to close.
* @param closeStatus
* The status to send.
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
logger.debug("afterConnectionClosed");
session.close(closeStatus);
try {
if (tunnel != null) {
tunnel.close();
}
} catch (GuacamoleException e) {
logger.debug("Unable to close WebSocket tunnel.", e);
}
}
@Override
public boolean supportsPartialMessages() {
return false;
}
private void process(WebSocketSession session, AtomicBoolean flag) {
if (flag.get()) {
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
GuacamoleReader reader = tunnel.acquireReader();
char[] readMessage;
try {
try {
// Attempt to read
while ((readMessage = reader.read()) != null) {
// Buffer message
buffer.append(readMessage);
// Flush if we expect to wait or buffer is getting
// full
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
session.sendMessage(new TextMessage(buffer));
buffer.setLength(0);
}
}
// No more data
String message = Integer.toString(GuacamoleStatus.SUCCESS.getGuacamoleStatusCode());
afterConnectionClosed(session,
new CloseStatus(GuacamoleStatus.SUCCESS.getWebSocketCode(), message));
}
// Catch any thrown guacamole exception and attempt
// to pass within the WebSocket connection, logging
// each error appropriately.
catch (GuacamoleClientException e) {
logger.info("WebSocket connection terminated: {}", e.getMessage());
String message = Integer.toString(e.getStatus().getGuacamoleStatusCode());
afterConnectionClosed(session, new CloseStatus(e.getStatus().getWebSocketCode(), message));
} catch (GuacamoleConnectionClosedException e) {
logger.error("Connection to guacd closed.", e);
if(flag.getAndSet(false)) {
process(session, flag);
}else {
String message = Integer.toString(GuacamoleStatus.SUCCESS.getGuacamoleStatusCode());
afterConnectionClosed(session,
new CloseStatus(GuacamoleStatus.SUCCESS.getWebSocketCode(), message));
}
} catch (GuacamoleException e) {
logger.error("Connection to guacd terminated abnormally ", e);
if(flag.getAndSet(false)) {
process(session, flag);
}else{
String message = Integer.toString(e.getStatus().getGuacamoleStatusCode());
afterConnectionClosed(session, new CloseStatus(e.getStatus().getWebSocketCode(), message));
}
}
} catch (Exception e) {
logger.debug("I/O error prevents further reads.", e);
}
}
}
}

View File

@ -1,114 +0,0 @@
package net.educoder.bridge.webssh.handler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import net.educoder.bridge.common.utils.Base64Util;
import net.educoder.bridge.common.utils.ThreadUtil;
import net.educoder.bridge.webssh.utils.RedisHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
/**
* 仅运行通过websocket回传结果
*
* @author 威少
*/
@Component
public class RunOnlyHandler extends TextWebSocketHandler {
private static final Logger logger = LoggerFactory.getLogger(RunOnlyHandler.class);
private final static String TYPE = "tp";
private final static String LOG_TYPE = "log";
private final static String DATA = "data";
private final static String PROCESS_FINISHED = "ProcessFinished";
private final static String RUN_ONLY_RESULT_KEY_PREFIX = "runOnlyResult";
private final static String CASE_OUTPUT_SEPARATOR = "\\x1b\\x09\\x1d";
@Autowired
private RedisHelper redisHelper;
@Override
public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception {
super.afterConnectionEstablished(wsSession);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
logger.info("仅运行socket连接接收到数据{}, session: {}, thread: {}", payload, session.getId(), Thread.currentThread().getId());
JSONObject msg = JSON.parseObject(payload);
if (LOG_TYPE.equals(msg.getString(TYPE))) {
String runOnlyResultKey = RUN_ONLY_RESULT_KEY_PREFIX + msg.getString(DATA);
// webssh先收到请求需要等待一会
int i = 0;
while (!redisHelper.hasKey(runOnlyResultKey) && i < 5 * 10) {
ThreadUtil.sleepSilently(100);
i++;
}
String runOnlyResult = redisHelper.get(runOnlyResultKey);
while (CASE_OUTPUT_SEPARATOR.equals(runOnlyResult) && redisHelper.hasKey(runOnlyResultKey)) {
ThreadUtil.sleepSilently(100);
runOnlyResult = redisHelper.get(runOnlyResultKey);
}
if (runOnlyResult != null) {
sendMsg(session, runOnlyResult);
}
}
super.handleTextMessage(session, message);
close(session);
}
@Override
public void afterConnectionClosed(WebSocketSession wsSession, CloseStatus status) throws Exception {
logger.info("afterConnectionClosed, session: {}", wsSession.getId());
close(wsSession);
super.afterConnectionClosed(wsSession, status);
}
/**
* 关闭连接
*
* @param session session
*/
public void close(WebSocketSession session) {
sendMsg(session, PROCESS_FINISHED);
ThreadUtil.sleepSilently(500);
if (session.isOpen()) {
try {
session.close();
logger.info("session {} closed", session.getId());
} catch (Exception e) {
logger.error("关闭仅运行socket连接失败, session: {}", session.getId(), e);
}
}
}
/**
* 发送数据
*
* @param session session
* @param msg 信息
*/
private void sendMsg(WebSocketSession session, String msg) {
try {
session.sendMessage(new TextMessage(Base64Util.encode(msg)));
logger.info("session {}, send msg: {} ", session, msg);
} catch (IllegalStateException | IOException e) {
logger.warn("发送内容异常, session: {}, e: {}", session.getId(), e.getMessage());
}
}
}

View File

@ -1,21 +1,86 @@
package net.educoder.bridge.webssh.model;
import lombok.Data;
@Data
public class ConnectInfo {
private String host;
private String port;
private String username;
private String secret;
private String myshixun_id;
private String gameid;
private int rows;
private int columns;
private int width;
private int height;
public String getTpiID() {
return myshixun_id;
public int getRows() {
return rows;
}
public void setRows(int rows) {
this.rows = rows;
}
public int getColumns() {
return columns;
}
public void setColumns(int columns) {
this.columns = columns;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return Integer.parseInt(port);
}
public void setPort(String port) {
this.port = port;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public String getGameid() {
return gameid;
}
public void setGameid(String gameid) {
this.gameid = gameid;
}
}

View File

@ -29,7 +29,11 @@ import net.educoder.bridge.common.utils.Base64Util;
@Service
public class JchService {
@Autowired
private WebsshService websshService;
@Autowired
private WebsshOverTimeService overTimeService;
private static List<WebscoketObj> sessionQueue = new CopyOnWriteArrayList<>();
private Logger logger = LoggerFactory.getLogger(getClass());
@ -66,19 +70,22 @@ public class JchService {
/**
* 处理客户端发过来的数据
*
*
* @param buffer
*/
public void recv(String buffer, WebSocketSession session) {
logger.debug("webssh收到数据{}", buffer);
WebscoketObj webscoketObj = findBySession(session);
boolean overtime = Boolean.FALSE;
try {
// logger.debug("recv函数进程{},sessionID:{},信息:{}",
// Thread.currentThread().getId(), session.getId(), buffer);
JSONObject object = JSONObject.parseObject(buffer);
String tp = object.getString("tp");
if ("init".equals(tp)) {
// 初始化连接
// {"tp":"init","data":{"host":"106.75.96.108","port":"41080","username":"root","secret":"123123","myshixun_id":"1080","rows":"30"}}
// {"tp":"init","data":{"host":"106.75.96.108","port":"41080","username":"root","secret":"123123","gameid":"1080","rows":"30"}}
ConnectInfo connectInfo = object.getObject("data", ConnectInfo.class);
if (webscoketObj != null) {
WebscoketObj finalWebscoketObj = webscoketObj;
@ -91,6 +98,7 @@ public class JchService {
});
}
} else if ("client".equals(tp)) {
overtime = Boolean.TRUE;
String data = object.getString("data");
if (webscoketObj != null) {
transTossh(webscoketObj.getOutputStream(), data);
@ -106,10 +114,12 @@ public class JchService {
connectInfo.getWidth(), connectInfo.getHeight());
}
} else if ("overtime".equals(tp)) {
overtime = Boolean.TRUE;
ConnectInfo connectInfo = webscoketObj.getConnectInfo();
logger.info("前端主动延长pod存活时间, 不需要通过此接口进行延长了:{}, tpiID:{},host:{},端口:{}", session.getId(),
connectInfo.getTpiID(), connectInfo.getHost(), connectInfo.getPort());
if (webscoketObj.getConnectInfo() != null) {
logger.info("前端主动延长pod存活时间, websocket:{}, tpiID:{},host:{},端口:{}", session.getId(),
connectInfo.getGameid(), connectInfo.getHost(), connectInfo.getPort());
}
}
} catch (Exception e) {
logger.error("转发 websocket {}命令到ssh出错: ", session.getId(), e);
@ -117,7 +127,15 @@ public class JchService {
closeByWebsocket(session);
}
// 延长pod存活时间
try {
if (webscoketObj.getConnectInfo() != null && overtime) {
String gameId = webscoketObj.getConnectInfo().getGameid();
overTimeService.websshOverTime(gameId);
}
} catch (Exception e) {
logger.error("pod 接收消息出错", e);
}
}
private void transTossh(OutputStream outputStream, String data) throws IOException {
@ -128,7 +146,7 @@ public class JchService {
}
private void connectTossh(WebscoketObj webscoketObj, ConnectInfo connectInfo, WebSocketSession webSocketSession) {
Session session = null;
Session session = null;
try {
JSch jsch = new JSch();
JSch.setLogger(jschLogger);
@ -136,7 +154,7 @@ public class JchService {
// 启动线程
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session = jsch.getSession(connectInfo.getUsername(), connectInfo.getHost(), Integer.parseInt(connectInfo.getPort()));
session = jsch.getSession(connectInfo.getUsername(), connectInfo.getHost(), connectInfo.getPort());
session.setConfig(config);
session.setPassword(connectInfo.getSecret());
@ -190,7 +208,8 @@ public class JchService {
webscoketObj.setConnectInfo(connectInfo);
logger.info("websocket {} open连接: tpiId:{},host:{},端口:{}", webSocketSession.getId(), connectInfo.getTpiID(),
websshService.active("" + connectInfo.getGameid());
logger.info("websocket {} open连接: tpiId:{},host:{},端口:{}", webSocketSession.getId(), connectInfo.getGameid(),
connectInfo.getHost(), connectInfo.getPort());
// 循环读取
@ -201,7 +220,7 @@ public class JchService {
String str = webSocketSession.isOpen() ? "is not" : "is";
str = webscoketObj.getSession().getId() + " " + str;
str = String.format("websocket %s closed.ssh读取终止连接: tpiId:%s,host:%s,端口:%s", str,
connectInfo.getTpiID(), connectInfo.getHost(), connectInfo.getPort());
connectInfo.getGameid(), connectInfo.getHost(), connectInfo.getPort());
if (webSocketSession.isOpen()) {
logger.error(str);
} else {
@ -213,7 +232,7 @@ public class JchService {
}
} catch (Exception e) {
logger.error("连接关闭: websocketId:" + webscoketObj.getSession().getId() + ", tpiId:" + connectInfo.getTpiID()
logger.error("连接关闭: websocketId:" + webscoketObj.getSession().getId() + ", tpiId:" + connectInfo.getGameid()
+ ", host:" + connectInfo.getHost() + ",端口: " + connectInfo.getPort(), e);
} finally {
if (session != null) {
@ -275,7 +294,7 @@ public class JchService {
}
this._close(webscoketObj);
}
public void closeByWebsocket(WebSocketSession session) {
WebscoketObj webscoketObj = findBySession(session);
sessionQueue.remove(webscoketObj);
@ -284,20 +303,20 @@ public class JchService {
return;
} else {
ConnectInfo info = webscoketObj.getConnectInfo();
logger.info("websocket {}连接中断 tpiId {} ", session.getId(), info.getTpiID());
logger.info("websocket {}连接中断 tpiId {} ", session.getId(), info.getGameid());
this._close(webscoketObj);
}
}
/**
* 通过gameId来匹配
*
*
* @param gameId
*/
public int findExistConnectByGameId(String gameId) {
long count = sessionQueue.stream().filter(webscoketObj -> webscoketObj.getConnectInfo() != null
&& gameId.equals(webscoketObj.getConnectInfo().getTpiID())).count();
&& gameId.equals(webscoketObj.getConnectInfo().getGameid())).count();
logger.debug("当前gameID对应的websocket数目{}", count);
return (int) count;
}

View File

@ -9,12 +9,12 @@ import javax.annotation.PostConstruct;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
//@Service
@Service
public class WebsshOverTimeService {
private Logger logger = LoggerFactory.getLogger(getClass());
// @Autowired
@Autowired
private WebsshService websshService;
private static ConcurrentHashMap<String, String> recvGameIdMap = new ConcurrentHashMap<>();
@ -45,7 +45,7 @@ public class WebsshOverTimeService {
}
if (recvGameIds.length() > 0) {
recvGameIds.setLength(recvGameIds.length() - 1);
// websshService.overTime(recvGameIds.toString());
websshService.overTime(recvGameIds.toString());
}
} catch (Exception e) {
logger.error("发送接收到websocket消息 tpi失败", e);

View File

@ -1,29 +0,0 @@
package net.educoder.bridge.webssh.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
/**
* redis工具类
*/
@Component
public class RedisHelper {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 读取缓存
*/
public String get(final String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 是否存在key
*/
public boolean hasKey(final String key) {
return redisTemplate.hasKey(key);
}
}

View File

@ -18,37 +18,4 @@ git.port=30122
git.backUpIP=pre-git.educoder.net
git.backUpDirs=/data/repositories/2018,/data/repositories/2018-2
git.repoDir=/data/repositories
spring.redis.host=127.0.0.1
spring.redis.port=6379
#guacamole
guacamole.server.ip=121.40.224.66
guacamole.server.port=54482
# mysql
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://rm-bp13v5020p7828r5rso.mysql.rds.aliyuncs.com:3306/testbridge?useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false
spring.datasource.username=testeducoder
spring.datasource.password=TEST@123
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=200
spring.datasource.maxActive=400
spring.datasource.minIdle=200
spring.datasource.validationQuery=select 1
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.testWhileIdle=true
#mybatis.config-location=classpath:mybatis-config.xml
mybatis.type-aliases-package=net.educoder.bridge.**.model
mybatis.mapper-locations=classpath:mapper/*.xml
#mapper
mapper.mappers=tk.mybatis.springboot.util.MyMapper
mapper.not-empty=false
mapper.identity=MYSQL
#showSql
#logging.level.com.example.demo.dao=debug
git.repoDir=/data/repositories

View File

@ -39,7 +39,6 @@
<!-- 屏蔽框架输出 -->
<logger name="org.slf4j" level="ERROR" />
<logger name="org.springframework" level="ERROR" />
<logger name="io.swagger" level="ERROR" />
<logger name="ch.qos.logback" level="OFF" />
<logger name="springfox.documentation" level="ERROR" />
<logger name="com.spotify.docker.client" level="ERROR" />

View File

@ -1,191 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="net.educoder.bridge.dao.WindowsInfoMapper" >
<resultMap id="BaseResultMap" type="net.educoder.bridge.common.model.WindowsInfo" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="uniq_id" property="uniqId" jdbcType="VARCHAR" />
<result column="instance_id" property="instanceId" jdbcType="VARCHAR" />
<result column="user_id" property="userID" jdbcType="VARCHAR" />
<result column="port" property="port" jdbcType="VARCHAR" />
<result column="vnc_port" property="vncPort" jdbcType="VARCHAR" />
<result column="template_name" property="templateName" jdbcType="VARCHAR" />
<result column="auto_release_time" property="autoReleaseTime" jdbcType="TIMESTAMP" />
<result column="forward_table_id" property="forwardTableId" jdbcType="VARCHAR" />
<result column="forward_entry_id" property="forwardEntryId" jdbcType="VARCHAR" />
<result column="vnc_forward_entry_id" property="vncForwardEntryId" jdbcType="VARCHAR" />
<result column="create_time" property="createTime" jdbcType="TIMESTAMP" />
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP" />
<result column="status" property="status" jdbcType="INTEGER" />
</resultMap>
<sql id="Base_Column_List" >
id, uniq_id, instance_id, user_id, port, vnc_port, template_name, auto_release_time, forward_table_id, forward_entry_id, vnc_forward_entry_id, status
</sql>
<select id="selectByUniqId" resultMap="BaseResultMap" parameterType="java.lang.String" >
select
<include refid="Base_Column_List" />
from windows_info
where uniq_id = #{uniqId,jdbcType=VARCHAR} and status = 0 limit 1
</select>
<select id="selectByUserId" resultMap="BaseResultMap" parameterType="java.lang.String" >
select
<include refid="Base_Column_List" />
from windows_info
where user_id = #{userID,jdbcType=VARCHAR}
and status = 0
</select>
<select id="selectByTpiId" resultMap="BaseResultMap" parameterType="java.lang.String" >
select
<include refid="Base_Column_List" />
from windows_info
where uniq_id like concat( #{tpiId,jdbcType=VARCHAR} , '%') and status = 0
</select>
<select id="selectUniqIdByAutoReleaseTime" resultType="java.lang.String" parameterType="net.educoder.bridge.common.model.WindowsInfo">
SELECT uniq_id FROM windows_info
where status = 0
<if test="autoReleaseTime != null">
and auto_release_time &lt; #{autoReleaseTime,jdbcType=TIMESTAMP}
</if>
</select>
<select id="selectNotForwardEntryHost" resultMap="BaseResultMap" >
SELECT
<include refid="Base_Column_List" />
FROM windows_info
WHERE (forward_entry_id IS NULL OR (vnc_port IS NOT NULL AND vnc_forward_entry_id IS NULL)) and status = 0
</select>
<delete id="deleteByUniqId" parameterType="java.lang.String" >
update windows_info set status = -1
where uniq_id = #{uniqId,jdbcType=VARCHAR}
</delete>
<insert id="insertSelective" useGeneratedKeys="true" keyProperty="id"
parameterType="net.educoder.bridge.common.model.WindowsInfo" >
insert into windows_info
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="id != null" >
id,
</if>
<if test="uniqId != null" >
uniq_id,
</if>
<if test="instanceId != null" >
instance_id,
</if>
<if test="userID != null" >
user_id,
</if>
<if test="port != null" >
port,
</if>
<if test="vncPort != null" >
vnc_port,
</if>
<if test="templateName != null" >
template_name,
</if>
<if test="autoReleaseTime != null" >
auto_release_time,
</if>
<if test="forwardTableId != null" >
forward_table_id,
</if>
<if test="forwardEntryId != null" >
forward_entry_id,
</if>
<if test="vncForwardEntryId != null" >
vnc_forward_entry_id,
</if>
<if test="createTime != null" >
create_time,
</if>
<if test="updateTime != null" >
update_time,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="id != null" >
#{id,jdbcType=BIGINT},
</if>
<if test="uniqId != null" >
#{uniqId,jdbcType=VARCHAR},
</if>
<if test="instanceId != null" >
#{instanceId,jdbcType=VARCHAR},
</if>
<if test="userID != null" >
#{userID,jdbcType=VARCHAR},
</if>
<if test="port != null" >
#{port,jdbcType=VARCHAR},
</if>
<if test="vncPort != null" >
#{vncPort,jdbcType=VARCHAR},
</if>
<if test="templateName != null" >
#{templateName,jdbcType=VARCHAR},
</if>
<if test="autoReleaseTime != null" >
#{autoReleaseTime,jdbcType=TIMESTAMP},
</if>
<if test="forwardTableId != null" >
#{forwardTableId,jdbcType=VARCHAR},
</if>
<if test="forwardEntryId != null" >
#{forwardEntryId,jdbcType=VARCHAR},
</if>
<if test="vncForwardEntryId != null" >
#{vncForwardEntryId,jdbcType=VARCHAR},
</if>
<if test="createTime != null" >
#{createTime,jdbcType=TIMESTAMP},
</if>
<if test="updateTime != null" >
#{updateTime,jdbcType=TIMESTAMP},
</if>
</trim>
</insert>
<update id="updateByUniqIdSelective" parameterType="net.educoder.bridge.common.model.WindowsInfo" >
update windows_info
<set >
<if test="instanceId != null" >
instance_id = #{instanceId,jdbcType=VARCHAR},
</if>
<if test="userID != null" >
user_id = #{userID,jdbcType=VARCHAR},
</if>
<if test="port != null" >
port = #{port,jdbcType=INTEGER},
</if>
<if test="vncPort != null" >
vnc_port = #{vncPort,jdbcType=INTEGER},
</if>
<if test="templateName != null" >
template_name = #{templateName,jdbcType=VARCHAR},
</if>
<if test="autoReleaseTime != null" >
auto_release_time = #{autoReleaseTime,jdbcType=TIMESTAMP},
</if>
<if test="forwardTableId != null" >
forward_table_id = #{forwardTableId,jdbcType=VARCHAR},
</if>
<if test="forwardEntryId != null" >
forward_entry_id = #{forwardEntryId,jdbcType=VARCHAR},
</if>
<if test="vncForwardEntryId != null" >
vnc_forward_entry_id = #{vncForwardEntryId,jdbcType=VARCHAR},
</if>
<if test="createTime != null" >
create_time = #{createTime,jdbcType=TIMESTAMP},
</if>
<if test="updateTime != null" >
update_time = #{updateTime,jdbcType=TIMESTAMP},
</if>
</set>
where uniq_id = #{uniqId,jdbcType=VARCHAR}
</update>
</mapper>