Compare commits
5 Commits
master
...
supercompu
| Author | SHA1 | Date |
|---|---|---|
|
|
86b8e46ce2 | |
|
|
d03b6dae16 | |
|
|
a61ce75af9 | |
|
|
66e802c750 | |
|
|
9cdca3296b |
6
pom.xml
6
pom.xml
|
|
@ -143,7 +143,11 @@
|
|||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.7.2</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
package net.educoder.bridge.common.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* shell执行结果
|
||||
*
|
||||
* @author 威少
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CmdResult {
|
||||
/**
|
||||
* 退出码
|
||||
*/
|
||||
private Integer exitStatus;
|
||||
/**
|
||||
* 实际输出
|
||||
*/
|
||||
private String out;
|
||||
|
||||
public enum ExitStatus {
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
SUCCESS(0),
|
||||
/**
|
||||
* 超时
|
||||
*/
|
||||
TIMEOUT(124),
|
||||
/**
|
||||
* 默认失败
|
||||
*/
|
||||
FAIL(-1);
|
||||
|
||||
private final int code;
|
||||
ExitStatus(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package net.educoder.bridge.webssh.controller;
|
||||
|
||||
import net.educoder.bridge.common.model.ApiResult;
|
||||
import net.educoder.bridge.webssh.service.JchService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/ssh")
|
||||
public class SshController {
|
||||
@Autowired
|
||||
private JchService jchService;
|
||||
|
||||
@GetMapping("/exec")
|
||||
public ApiResult<String> exec(String cmd,String username,String secret,String host,Integer port) throws Exception {
|
||||
ApiResult<String> r = new ApiResult<>();
|
||||
String s = jchService.sshCommand(host, port, username, secret, cmd);
|
||||
r.setMsg("success");
|
||||
r.setData(s);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package net.educoder.bridge.webssh.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.educoder.bridge.common.model.ApiResult;
|
||||
import net.educoder.bridge.common.model.CmdResult;
|
||||
import net.educoder.bridge.webssh.service.WinService;
|
||||
import net.educoder.bridge.webssh.utils.CmdUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* 在windows操作
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/win")
|
||||
public class WinController {
|
||||
|
||||
@Autowired
|
||||
private WinService winService;
|
||||
|
||||
@GetMapping("/exec")
|
||||
public ApiResult<String> exec(String cmd){
|
||||
log.info("win exec cmd:{}",cmd);
|
||||
ApiResult<String> r = new ApiResult<>();
|
||||
|
||||
CmdResult result = CmdUtil.executeAndGetExitStatus(cmd);
|
||||
|
||||
r.setCode(result.getExitStatus());
|
||||
r.setMsg(result.getOut());
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/remote/upload")
|
||||
public ApiResult<String> uploadFile(@RequestParam("file") MultipartFile file, String tpiId, String remoteTargetDir, String username, String secret, String host, Integer port) {
|
||||
log.info("win upload file to ssh--tpiId:{}",tpiId);
|
||||
ApiResult<String> r = new ApiResult<>();
|
||||
r.setCode(0);
|
||||
r.setMsg("success");
|
||||
|
||||
if (null!=file){
|
||||
log.info("***uploadFile filename:{}; size:{}" ,file.getOriginalFilename(),file.getSize());
|
||||
}
|
||||
try {
|
||||
winService.uploadFileToSsh(tpiId,file,remoteTargetDir,username,secret,host,port);
|
||||
} catch (Exception e) {
|
||||
log.error("win upload file to ssh ERROR--tpiId:{}; e:{}",tpiId,e);
|
||||
r.setCode(500);
|
||||
r.setMsg("error");
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@GetMapping("/remote/download")
|
||||
public void downloadFile(String tpiId, String remoteSrcDir , String username, String secret, String host, Integer port, HttpServletResponse response) {
|
||||
log.info("win download file by ssh--tpiId:{}",tpiId);
|
||||
File zipDir = null;
|
||||
try {
|
||||
zipDir = winService.downloadFileDir(tpiId,remoteSrcDir,username,secret,host,port);
|
||||
String fileName = zipDir.getName();
|
||||
response.setCharacterEncoding("utf-8");
|
||||
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
|
||||
response.setContentType("application/zip");
|
||||
try (FileInputStream fileInputStream = new FileInputStream(zipDir);
|
||||
OutputStream outputStream = response.getOutputStream()) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = fileInputStream.read(buffer)) != -1) {
|
||||
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
outputStream.flush();
|
||||
}
|
||||
}catch (Exception e){
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
log.error("win download file by ssh ERROR--tpiId:{}; e:{}",tpiId,e);
|
||||
}finally {
|
||||
if (zipDir!=null && zipDir.exists()){
|
||||
zipDir.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package net.educoder.bridge.webssh.service;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
|
@ -10,6 +11,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.jcraft.jsch.*;
|
||||
import net.educoder.bridge.webssh.utils.AESEncryptionUtil;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
|
@ -18,10 +22,6 @@ import org.springframework.web.socket.TextMessage;
|
|||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.jcraft.jsch.ChannelShell;
|
||||
import com.jcraft.jsch.JSch;
|
||||
import com.jcraft.jsch.Session;
|
||||
import com.jcraft.jsch.UserInfo;
|
||||
|
||||
import net.educoder.bridge.webssh.model.ConnectInfo;
|
||||
import net.educoder.bridge.webssh.model.WebscoketObj;
|
||||
|
|
@ -77,6 +77,16 @@ public class JchService {
|
|||
JSONObject object = JSONObject.parseObject(buffer);
|
||||
String tp = object.getString("tp");
|
||||
if ("init".equals(tp)) {
|
||||
// 先将ssh信息解密,放入原json。
|
||||
JSONObject jsonData =object.getJSONObject("data");
|
||||
JSONObject sshinfo = JSONObject.parseObject(AESEncryptionUtil.decrypt(jsonData.getString("ssh_info")));
|
||||
if (sshinfo!=null){
|
||||
jsonData.put("host",sshinfo.getString("host"));
|
||||
jsonData.put("port",sshinfo.getString("port"));
|
||||
jsonData.put("username",sshinfo.getString("username"));
|
||||
jsonData.put("secret",sshinfo.getString("secret"));
|
||||
}
|
||||
logger.info("** ws_jsonData **:"+jsonData);
|
||||
// 初始化连接
|
||||
// {"tp":"init","data":{"host":"106.75.96.108","port":"41080","username":"root","secret":"123123","myshixun_id":"1080","rows":"30"}}
|
||||
ConnectInfo connectInfo = object.getObject("data", ConnectInfo.class);
|
||||
|
|
@ -310,4 +320,29 @@ public class JchService {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接操作机,执行命令
|
||||
*/
|
||||
public String sshCommand(String host, int port, String username, String password, String command) throws JSchException, IOException {
|
||||
JSch jsch = new JSch();
|
||||
Session session = jsch.getSession(username, host, port);
|
||||
//避免SSH 的公钥检查
|
||||
session.setConfig("StrictHostKeyChecking", "no");
|
||||
session.setPassword(password);
|
||||
session.connect();
|
||||
|
||||
ChannelExec channelExec = (ChannelExec) session.openChannel("exec");
|
||||
InputStream in = channelExec.getInputStream();
|
||||
channelExec.setCommand(command);
|
||||
channelExec.setErrStream(System.err);
|
||||
channelExec.connect();
|
||||
String out = IOUtils.toString(in, StandardCharsets.UTF_8);
|
||||
|
||||
in.close();
|
||||
channelExec.disconnect();
|
||||
session.disconnect();
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
package net.educoder.bridge.webssh.service;
|
||||
|
||||
import com.jcraft.jsch.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.educoder.bridge.webssh.utils.CmdUtil;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* @author jshixiong
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WinService {
|
||||
public static final String WINDOWS_WORKSPACE = "C:\\data\\workspace\\";
|
||||
|
||||
/**
|
||||
* 文件上传目标路径
|
||||
*/
|
||||
private static final String FILE_OP_DESTINATION = "userfiles";
|
||||
|
||||
/**
|
||||
* 文件下载目录路径
|
||||
*/
|
||||
private static final String FILE_DL_DESTINATION = "downloadfiles";
|
||||
|
||||
/**
|
||||
* 上传文件到远程目录
|
||||
*
|
||||
* @param tpiId tpiId
|
||||
* @param file 上传的文件
|
||||
* @param remoteTargetDir 远程文件夹
|
||||
*/
|
||||
public void uploadFileToSsh(String tpiId, MultipartFile file, String remoteTargetDir , String username, String secret, String host, Integer port) throws Exception {
|
||||
String localFileDir = WINDOWS_WORKSPACE + tpiId + File.separator + FILE_OP_DESTINATION;
|
||||
CmdUtil.executeAndGetExitStatus("mkdir " + localFileDir);
|
||||
//文件windows放本地
|
||||
File localFile = new File(localFileDir + File.separator + file.getOriginalFilename());
|
||||
FileUtils.copyInputStreamToFile(file.getInputStream(),localFile);
|
||||
|
||||
JSch jsch = new JSch();
|
||||
Session session = jsch.getSession(username, host, port);
|
||||
session.setPassword(secret);
|
||||
session.setConfig("StrictHostKeyChecking", "no");
|
||||
session.connect();
|
||||
//创建远程文件夹
|
||||
ChannelExec channelExec = (ChannelExec) session.openChannel("exec");
|
||||
channelExec.setCommand("mkdir -p " + remoteTargetDir);
|
||||
channelExec.connect();
|
||||
//上传
|
||||
log.info("uploadFileToSsh: {}==>{}",localFile.getAbsolutePath(),remoteTargetDir + "/" + file.getOriginalFilename());
|
||||
ChannelSftp channelSftp = (ChannelSftp) session.openChannel("sftp");
|
||||
channelSftp.connect();
|
||||
channelSftp.put(localFile.getAbsolutePath(), remoteTargetDir + "/" + file.getOriginalFilename());
|
||||
|
||||
channelExec.disconnect();
|
||||
channelSftp.disconnect();
|
||||
session.disconnect();
|
||||
//删除windows存放的文件
|
||||
CmdUtil.executeAndGetExitStatus("rmdir /s /q " + localFileDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将远程文件夹打包下载到windows工作目录
|
||||
*
|
||||
* @param tpiId tpiId
|
||||
* @param remoteSrcDir 需下载的远程文件夹
|
||||
*/
|
||||
public File downloadFileDir(String tpiId, String remoteSrcDir , String username, String secret, String host, Integer port) throws Exception {
|
||||
String localFileDir = WINDOWS_WORKSPACE + tpiId + File.separator + FILE_DL_DESTINATION;
|
||||
CmdUtil.executeAndGetExitStatus("mkdir " + localFileDir);
|
||||
//从ssh远程下载所有文件到windows
|
||||
JSch jsch = new JSch();
|
||||
Session session = jsch.getSession(username, host, port);
|
||||
session.setPassword(secret);
|
||||
session.setConfig("StrictHostKeyChecking", "no");
|
||||
session.connect();
|
||||
//远程打包
|
||||
String zipFile = remoteSrcDir + ".zip";
|
||||
ChannelExec channelExec = (ChannelExec) session.openChannel("exec");
|
||||
InputStream in = channelExec.getInputStream();
|
||||
channelExec.setCommand("cd " + remoteSrcDir + " && rm -rf " + zipFile + " && zip -qr " + zipFile + " .");
|
||||
channelExec.connect();
|
||||
String out = IOUtils.toString(in, StandardCharsets.UTF_8);
|
||||
log.info("打包=={}",out);
|
||||
//获取远程zip文件
|
||||
String localZipFile = localFileDir + File.separator + zipFile.substring(zipFile.lastIndexOf("/")+1);
|
||||
ChannelSftp channelSftp = (ChannelSftp) session.openChannel("sftp");
|
||||
channelSftp.connect();
|
||||
channelSftp.get(zipFile, localZipFile);
|
||||
|
||||
//关闭连接
|
||||
in.close();
|
||||
channelExec.disconnect();
|
||||
channelSftp.disconnect();
|
||||
session.disconnect();
|
||||
//打包文件夹
|
||||
return new File(localZipFile);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package net.educoder.bridge.webssh.utils;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* AES对称加密
|
||||
* @author jshixiong
|
||||
*/
|
||||
public class AESEncryptionUtil {
|
||||
private static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";
|
||||
/**
|
||||
* 16位key
|
||||
*/
|
||||
private static final String ENCRYPTION_KEY = "MyKey@zq12345678";
|
||||
/**
|
||||
* 16位IV
|
||||
*/
|
||||
private static final String INIT_VECTOR = "MyIV@zq123456789";
|
||||
|
||||
/**
|
||||
* AES加密
|
||||
*/
|
||||
public static String encrypt(String message) {
|
||||
try {
|
||||
IvParameterSpec iv = new IvParameterSpec(INIT_VECTOR.getBytes(StandardCharsets.UTF_8));
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec((ENCRYPTION_KEY).getBytes(StandardCharsets.UTF_8), "AES");
|
||||
|
||||
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);
|
||||
|
||||
byte[] encryptedBytes = cipher.doFinal(message.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getEncoder().encodeToString(encryptedBytes);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密
|
||||
*/
|
||||
public static String decrypt(String encryptedMessage) {
|
||||
try {
|
||||
IvParameterSpec iv = new IvParameterSpec(INIT_VECTOR.getBytes(StandardCharsets.UTF_8));
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec((ENCRYPTION_KEY).getBytes(StandardCharsets.UTF_8), "AES");
|
||||
|
||||
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
|
||||
|
||||
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedMessage);
|
||||
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
|
||||
return new String(decryptedBytes, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package net.educoder.bridge.webssh.utils;
|
||||
|
||||
import net.educoder.bridge.common.model.CmdResult;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
* windows命令行工具
|
||||
* @author jshixiong
|
||||
*/
|
||||
public class CmdUtil {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CmdUtil.class);
|
||||
|
||||
/**
|
||||
* 执行cmd命令并获取输出
|
||||
*/
|
||||
public static String execute(String command) {
|
||||
return executeAndGetExitStatus(command).getOut();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行cmd命令并获得输出及退出码,失败重试 共尝试retryTimes次
|
||||
*/
|
||||
public static CmdResult executeAndGetExitStatus(String command, int retryTimes) {
|
||||
CmdResult result = new CmdResult();
|
||||
|
||||
for (int i = 0; i < retryTimes; i++) {
|
||||
result = executeAndGetExitStatus(command);
|
||||
if (result.getExitStatus() != 0) {
|
||||
logger.info("执行cmd错误, 再次执行 command: {}, result: {}, times: {}", command, result, i);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令并获得输出以及退出码
|
||||
*/
|
||||
public static CmdResult executeAndGetExitStatus(String command) {
|
||||
CmdResult result = new CmdResult();
|
||||
|
||||
StringBuilder out = new StringBuilder();
|
||||
Integer exitStatus = -1;
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", command);
|
||||
pb.redirectErrorStream(true);
|
||||
try {
|
||||
Process process = pb.start();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
out.append(line);
|
||||
out.append(System.getProperty("line.separator"));
|
||||
}
|
||||
exitStatus = process.waitFor();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("执行cmd出错, command:{}", command, e);
|
||||
}
|
||||
|
||||
result.setOut(out.toString().trim());
|
||||
result.setExitStatus(exitStatus);
|
||||
logger.debug("execute command: {}, out: {}, status: {}", command, out, exitStatus);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue