Compare commits
33 Commits
master
...
jenkins-en
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2f3a2ae28f | ||
![]() |
38ba61ed3a | ||
![]() |
6b8165028e | ||
![]() |
8189a825d3 | ||
![]() |
da63f37625 | ||
![]() |
be07dc0e0a | ||
![]() |
97ffb19c03 | ||
![]() |
2d4653b29a | ||
![]() |
66bc6a1250 | ||
![]() |
926c1af1e1 | ||
![]() |
f28fe4ecdf | ||
![]() |
56150c156b | ||
![]() |
891a332c59 | ||
![]() |
7cd4396001 | ||
![]() |
62b0caa3da | ||
![]() |
4815b590c4 | ||
![]() |
a238538106 | ||
![]() |
ea35ed1f0b | ||
![]() |
c26a183209 | ||
![]() |
4b8dfe978a | ||
![]() |
30c201a230 | ||
![]() |
818c2e0198 | ||
![]() |
a7398d265c | ||
![]() |
04afb442cf | ||
![]() |
b65bdd95bc | ||
![]() |
790193d0c9 | ||
![]() |
1028cc0705 | ||
![]() |
a7738d2add | ||
![]() |
1d8a1f6eb9 | ||
![]() |
db95a112bc | ||
![]() |
5410bd4374 | ||
![]() |
6642fc493b | ||
![]() |
6e317f9b8c |
3
.idea/compiler.xml
generated
3
.idea/compiler.xml
generated
@ -7,6 +7,7 @@
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="machine-management-module" />
|
||||
</profile>
|
||||
<profile name="Annotation profile for ops-pro" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
@ -53,6 +54,7 @@
|
||||
<module name="module-ci-dispatch-api" target="17" />
|
||||
<module name="module-ci-environment" target="17" />
|
||||
<module name="module-ci-event" target="17" />
|
||||
<module name="module-ci-execute" target="1.5" />
|
||||
<module name="module-ci-log" target="17" />
|
||||
<module name="module-ci-market" target="17" />
|
||||
<module name="module-ci-project" target="17" />
|
||||
@ -69,6 +71,7 @@
|
||||
<module name="app-plugins" options="-parameters" />
|
||||
<module name="commons" options="-parameters" />
|
||||
<module name="framework" options="-parameters" />
|
||||
<module name="machine-management-module" options="-parameters" />
|
||||
<module name="module-ci-commons" options="-parameters" />
|
||||
<module name="module-ci-engine" options="-parameters" />
|
||||
<module name="module-ci-plugin" options="-parameters" />
|
||||
|
1
.idea/encodings.xml
generated
1
.idea/encodings.xml
generated
@ -39,6 +39,7 @@
|
||||
<file url="file://$PROJECT_DIR$/framework/spring-boot-starter-websocket/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/framework/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/framework/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/modules/ee/machine-management-module/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/modules/module-ci-common-pipeline/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/modules/module-ci-common-pipeline/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/modules/module-ci-common/src/main/java" charset="UTF-8" />
|
||||
|
10
.idea/jarRepositories.xml
generated
10
.idea/jarRepositories.xml
generated
@ -1,16 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="huaweicloud" />
|
||||
<option name="name" value="huawei" />
|
||||
<option name="url" value="https://maven.aliyun.com/repository/public" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="huaweicloud" />
|
||||
<option name="name" value="huawei" />
|
||||
<option name="url" value="https://maven.aliyun.com/repository/public" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="aliyunmaven" />
|
||||
<option name="name" value="aliyun" />
|
||||
|
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@ -52,6 +52,7 @@
|
||||
<option value="$PROJECT_DIR$/modules/module-ci-store-api/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/modules/module-ci-process-biz/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/modules/module-ci-process-api/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/modules/ee/machine-management-module/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="ignoredFiles">
|
||||
|
82
modules/module-ci-machine/pom.xml
Normal file
82
modules/module-ci-machine/pom.xml
Normal file
@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>modules</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>module-ci-machine</artifactId>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
<version>${revision}</version>
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.aliyun.oss</groupId>
|
||||
<artifactId>aliyun-sdk-oss</artifactId>
|
||||
<version>3.15.1</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 机器连接-->
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jsch</artifactId>
|
||||
<version>0.1.55</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.32</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>3.5.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- SpringDoc OpenAPI 依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
<!--文件上传-->
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>module-infra-biz</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.13</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
@ -0,0 +1,85 @@
|
||||
package cd.casic.module.machine.Interceptor;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {
|
||||
@Override
|
||||
public boolean beforeHandshake(
|
||||
@NotNull ServerHttpRequest request,
|
||||
@NotNull ServerHttpResponse response,
|
||||
@NotNull WebSocketHandler wsHandler,
|
||||
@NotNull Map<String, Object> attributes
|
||||
) {
|
||||
log.info("WebSocket握手请求: {}", request.getURI());
|
||||
|
||||
// 从URL参数中获取id
|
||||
String id = extractIdFromUrl(request.getURI());
|
||||
|
||||
System.out.println("-----------------------------------------");
|
||||
if (id != null) {
|
||||
attributes.put("machineId", id); // 将id存入attributes
|
||||
log.info("握手验证成功 - ID: {}", id);
|
||||
return true;
|
||||
} else {
|
||||
response.setStatusCode(HttpStatus.BAD_REQUEST);
|
||||
log.warn("握手验证失败 - 缺少 id URL 参数");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterHandshake(
|
||||
@NotNull ServerHttpRequest request,
|
||||
@NotNull ServerHttpResponse response,
|
||||
@NotNull WebSocketHandler wsHandler,
|
||||
Exception exception
|
||||
) {
|
||||
if (exception == null) {
|
||||
log.info("WebSocket握手完成 - URI: {}",
|
||||
request.getURI());
|
||||
} else {
|
||||
log.error("WebSocket握手异常 - URI: {}, 异常信息: {}",
|
||||
request.getURI(),
|
||||
exception.getMessage(),
|
||||
exception);
|
||||
}
|
||||
}
|
||||
|
||||
// 从URI中提取id参数的辅助方法
|
||||
private String extractIdFromUrl(URI uri) {
|
||||
try {
|
||||
String query = uri.getQuery();
|
||||
if (query != null) {
|
||||
String[] params = query.split("&");
|
||||
for (String param : params) {
|
||||
String[] keyValue = param.split("=");
|
||||
if (keyValue.length == 2 && "id".equalsIgnoreCase(keyValue[0])) { // 修改为匹配id
|
||||
return URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("解析URL参数失败", e);
|
||||
throw exception(FAILED_TO_PARSE_URL_PARAMETERS);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
package cd.casic.module.machine.component;
|
||||
|
||||
import cd.casic.module.machine.dal.model.FileNode;
|
||||
import com.jcraft.jsch.Channel;
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.Session;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
import com.jcraft.jsch.SftpException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FileTreeComponent {
|
||||
|
||||
private ChannelSftp sftp;
|
||||
//todo缓存过期还未设置
|
||||
private final Map<String, FileNode> directoryCache = new ConcurrentHashMap<>();
|
||||
private static final long CACHE_EXPIRE_TIME = 60 * 1000; // 缓存过期时间:1分钟
|
||||
private final Map<String, Long> cacheTimeStamp = new ConcurrentHashMap<>();
|
||||
|
||||
// 将文件树转换为JSON友好的Map结构(仅一级目录)
|
||||
public static Map<String, Object> convertToMap(FileNode node) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("name", node.getName());
|
||||
map.put("isDirectory", node.isDirectory());
|
||||
map.put("size", node.getSize());
|
||||
map.put("permissions", node.getPermissions());
|
||||
map.put("modifiedTime", node.getModifiedTime());
|
||||
// 仅添加直接子项,不递归
|
||||
if (node.isDirectory() && !node.getChildren().isEmpty()) {
|
||||
List<Map<String, Object>> children = node.getChildren().stream()
|
||||
.map(FileTreeComponent::convertToMap)
|
||||
.collect(Collectors.toList());
|
||||
map.put("children", children);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// 获取指定路径的直接子项(不递归)
|
||||
public List<ChannelSftp.LsEntry> listDirectChildren(String path) {
|
||||
if (sftp == null || !isSftpConnected()) {
|
||||
log.error("SFTP连接无效,无法列出文件");
|
||||
throw exception(CONNECTION_LOST);
|
||||
}
|
||||
|
||||
List<ChannelSftp.LsEntry> entries = Collections.synchronizedList(new ArrayList<>());
|
||||
//定义ChannelSftp下LsEntrySelector接口中select方法
|
||||
ChannelSftp.LsEntrySelector selector = entry -> {
|
||||
entries.add(entry);
|
||||
return ChannelSftp.LsEntrySelector.CONTINUE;
|
||||
};
|
||||
|
||||
try {
|
||||
sftp.ls(path, selector);
|
||||
} catch (SftpException e) {
|
||||
log.error("读取远程文件目录结构失败 [错误码: {}, 信息: {}]",
|
||||
e.id, e.getMessage(), e);
|
||||
throw exception(READ_REMOTE_DIRECTORY_FAIL);
|
||||
}
|
||||
return new ArrayList<>(entries);
|
||||
}
|
||||
|
||||
// 创建文件节点(支持使用完整路径或文件名)
|
||||
private static FileNode createFileNode(String name, SftpATTRS attrs, boolean useFullPath) {
|
||||
String displayName = useFullPath ? name : name.substring(name.lastIndexOf('/') + 1);
|
||||
return new FileNode(
|
||||
displayName,
|
||||
attrs.isDir(),
|
||||
attrs.getSize(),
|
||||
attrs.getPermissionsString(),
|
||||
attrs.getMTime() * 1000L // 转换为毫秒
|
||||
);
|
||||
}
|
||||
|
||||
// 获取远程文件树的方法(仅展示下一级)
|
||||
public Map<String, Object> getRemoteFileTree(Session session, String path) {
|
||||
path = normalizePath(path);
|
||||
Channel channel = null;
|
||||
try {
|
||||
// 缓存有效性检查
|
||||
String cacheKey = "ls:" + path;
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
// 检查缓存是否存在且未过期
|
||||
if (directoryCache.containsKey(cacheKey) &&
|
||||
currentTime - cacheTimeStamp.getOrDefault(cacheKey, 0L) < CACHE_EXPIRE_TIME) {
|
||||
log.debug("从缓存获取目录内容: {}", path);
|
||||
// 构建缓存中的根节点
|
||||
FileNode root = directoryCache.get(cacheKey);
|
||||
return sortFileInfo(convertToMap(root));
|
||||
}
|
||||
|
||||
// 打开SFTP通道
|
||||
channel = session.openChannel("sftp");
|
||||
channel.connect();
|
||||
sftp = (ChannelSftp) channel;
|
||||
|
||||
// 先通过stat获取当前路径信息
|
||||
SftpATTRS rootAttrs = sftp.stat(path);
|
||||
FileNode root = createFileNode(path, rootAttrs, true);
|
||||
|
||||
// 仅获取直接子项,不递归
|
||||
List<ChannelSftp.LsEntry> entries = listDirectChildren(path);
|
||||
//循环添加子节点
|
||||
for (ChannelSftp.LsEntry entry : entries) {
|
||||
String fileName = entry.getFilename();
|
||||
if (fileName.equals(".") || fileName.equals("..")) continue;
|
||||
SftpATTRS attrs = entry.getAttrs();
|
||||
FileNode childNode = createFileNode(fileName, attrs, false);
|
||||
root.addChild(childNode);
|
||||
}
|
||||
// 更新缓
|
||||
directoryCache.put(cacheKey, root);
|
||||
cacheTimeStamp.put(cacheKey, currentTime);
|
||||
return sortFileInfo(convertToMap(root));
|
||||
} catch (JSchException e) {
|
||||
log.error("SFTP通道创建或连接失败: {}", e.getMessage(), e);
|
||||
if (e.getMessage().contains("open")) {
|
||||
throw exception(CREATE_CHANEL_ERROR);
|
||||
} else {
|
||||
throw exception(CHANEL_CONNECT_FAIL);
|
||||
}
|
||||
} catch (SftpException e) {
|
||||
if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
|
||||
// 仅捕获路径不存在的错误
|
||||
log.error("路径不存在: {}", path);
|
||||
throw exception(PATH_NOT_EXISTS); // 自定义异常
|
||||
} else if (e.id == ChannelSftp.SSH_FX_PERMISSION_DENIED) {
|
||||
// 处理权限问题(非路径不存在)
|
||||
log.error("无路径访问权限: {}", path);
|
||||
throw exception(NO_PATH_PERMISSION);
|
||||
} else {
|
||||
log.error("获取目录内容失败: {}", e.getMessage(), e);
|
||||
throw exception(READ_REMOTE_DIRECTORY_FAIL);
|
||||
}
|
||||
} finally {
|
||||
// 确保资源释放
|
||||
if (sftp != null) {
|
||||
try {
|
||||
sftp.disconnect();
|
||||
} catch (Exception ex) {
|
||||
log.warn("关闭SFTP连接失败", ex);
|
||||
}
|
||||
}
|
||||
if (channel != null && channel.isConnected()) {
|
||||
channel.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查SFTP连接状态
|
||||
private boolean isSftpConnected() {
|
||||
if (sftp == null) return false;
|
||||
try {
|
||||
sftp.pwd();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.warn("SFTP连接状态检查失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 规范化路径:确保末尾有斜杠(根路径除外)
|
||||
private String normalizePath(String path) {
|
||||
// 移除多余的斜杠(多个连续的斜杠会被替换为一个)
|
||||
path = path.replaceAll("/+", "/");
|
||||
|
||||
// 如果路径不为根路径且末尾没有斜杠,则添加斜杠
|
||||
if (!"/".equals(path) && !path.endsWith("/")) {
|
||||
path += "/";
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对文件和目录信息进行排序(仅处理直接子级)
|
||||
*
|
||||
* @param fileInfoMap 文件/目录信息映射
|
||||
* @return 排序后的映射
|
||||
*/
|
||||
public Map<String, Object> sortFileInfo(Map<String, Object> fileInfoMap) {
|
||||
// 检查是否包含子节点
|
||||
if (fileInfoMap.containsKey("children")) {
|
||||
List<Map<String, Object>> children = (List<Map<String, Object>>) fileInfoMap.get("children");
|
||||
// 对子节点列表进行排序
|
||||
if (children != null && !children.isEmpty()) {
|
||||
children.sort((a, b) -> {
|
||||
// 获取isDirectory属性并比较
|
||||
boolean isADirectory = (boolean) a.get("isDirectory");
|
||||
boolean isBDirectory = (boolean) b.get("isDirectory");
|
||||
// 目录排在文件前面
|
||||
return Boolean.compare(isBDirectory, isADirectory);
|
||||
});
|
||||
// 更新排序后的子节点列表
|
||||
fileInfoMap.put("children", children);
|
||||
}
|
||||
}
|
||||
return fileInfoMap;
|
||||
}
|
||||
}
|
@ -0,0 +1,230 @@
|
||||
package cd.casic.module.machine.component;
|
||||
|
||||
import cd.casic.framework.commons.exception.ServiceException;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
|
||||
import cd.casic.module.machine.enums.AuthenticationType;
|
||||
import cd.casic.module.machine.enums.ConnectionStatus;
|
||||
import cd.casic.module.machine.enums.SSHChanelType;
|
||||
import cd.casic.module.machine.service.SecretKeyService;
|
||||
import com.jcraft.jsch.Channel;
|
||||
import com.jcraft.jsch.ChannelExec;
|
||||
import com.jcraft.jsch.JSch;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.Session;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Properties;
|
||||
|
||||
import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cd.casic.module.machine.contants.MachineErrorCodeConstants.SECRET_KEY_NULL;
|
||||
import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*;
|
||||
|
||||
@Slf4j
|
||||
@Data
|
||||
@Component
|
||||
public class WebSocketConnection {
|
||||
private SecretKeyService secretKeyService;
|
||||
|
||||
private MachineInfoDO machineInfo;
|
||||
private ConnectionStatus connectionStatus = ConnectionStatus.DISCONNECTED;
|
||||
private Session sshSession;
|
||||
|
||||
public WebSocketConnection(SecretKeyService secretKeyService) {
|
||||
this.secretKeyService = secretKeyService;
|
||||
}
|
||||
|
||||
private static final int CONNECTION_TIMEOUT = 5000; // 连接超时时间(毫秒)
|
||||
|
||||
public void initConnection(MachineInfoDO machineInfo) {
|
||||
try {
|
||||
this.machineInfo = machineInfo;
|
||||
this.sshSession = doConnect(machineInfo);
|
||||
log.info("已成功建立 SSH 连接至 {} ", machineInfo.getHostIp());
|
||||
this.connectionStatus = ConnectionStatus.CONNECTING;
|
||||
} catch (ServiceException e) {
|
||||
log.warn("SSH 连接失败: {}", e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
if (sshSession != null && sshSession.isConnected()) {
|
||||
try {
|
||||
sshSession.disconnect();
|
||||
log.info("SSH连接关闭: {}", machineInfo.getHostIp());
|
||||
} catch (Exception e) {
|
||||
log.error("关闭SSH连接失败: {}", e.getMessage());
|
||||
throw exception(CLOSE_CLOSE_SESSION_ERROR);
|
||||
}
|
||||
}
|
||||
connectionStatus = ConnectionStatus.DISCONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行远程命令,支持超时和中断处理
|
||||
*/
|
||||
public void executeCommand(WebSocketSession webSocketSession, String command) {
|
||||
// 1. 检查连接状态
|
||||
if (sshSession == null || !sshSession.isConnected()) {
|
||||
sendErrorMessage(webSocketSession, "SSH连接未建立或已断开");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 2. 创建SSH命令执行通道
|
||||
Channel channel;
|
||||
try {
|
||||
channel = sshSession.openChannel(SSHChanelType.EXEC.getMessage());
|
||||
} catch (JSchException e) {
|
||||
throw exception(CREATE_CHANEL_ERROR);
|
||||
}
|
||||
((ChannelExec) channel).setCommand(command);
|
||||
// 3. 设置输入/输出流
|
||||
channel.setInputStream(null);
|
||||
((ChannelExec) channel).setErrStream(System.err);
|
||||
// 4. 获取命令输出流
|
||||
InputStream inputStream = channel.getInputStream();
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
BufferedReader reader = new BufferedReader(inputStreamReader);
|
||||
// 5. 连接并执行命令
|
||||
channel.connect();
|
||||
// 6. 读取命令输出并实时发送给客户端
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
// 实时发送输出到客户端
|
||||
webSocketSession.sendMessage(new TextMessage(line));
|
||||
}
|
||||
// 7. 等待命令执行完成
|
||||
int exitStatus = channel.getExitStatus();
|
||||
// 8. 发送命令执行完毕的消息
|
||||
webSocketSession.sendMessage(new TextMessage(
|
||||
"[系统] 命令执行完毕,退出状态: " + exitStatus
|
||||
));
|
||||
// 9. 关闭通道
|
||||
channel.disconnect();
|
||||
} catch (JSchException | IOException e) {
|
||||
throw exception(EXECUTE_COMMAND_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
// 发送错误消息的辅助方法
|
||||
public void sendErrorMessage(WebSocketSession webSocketSession, String message) {
|
||||
try {
|
||||
if (webSocketSession.isOpen()) {
|
||||
webSocketSession.sendMessage(new TextMessage("[错误] " + message));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("发送错误消息失败", e);
|
||||
throw exception(WEBSOCKET_SEND_MESSAGE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实际执行连接逻辑
|
||||
*/
|
||||
private Session doConnect(MachineInfoDO machineInfo) {
|
||||
JSch jsch = new JSch();
|
||||
// 配置认证方式
|
||||
configureAuthentication(jsch, machineInfo);
|
||||
Session session;
|
||||
// 创建SSH会话
|
||||
try {
|
||||
session = jsch.getSession(
|
||||
machineInfo.getUsername(),
|
||||
machineInfo.getHostIp(),
|
||||
machineInfo.getSshPort() != null ? machineInfo.getSshPort() : 22
|
||||
);
|
||||
} catch (JSchException e) {
|
||||
throw exception(CREATE_SESSION_ERROR);
|
||||
}
|
||||
// 配置连接参数
|
||||
configureSession(session, machineInfo);
|
||||
// 建立连接
|
||||
try {
|
||||
session.connect(CONNECTION_TIMEOUT);
|
||||
} catch (JSchException e) {
|
||||
throw exception(SESSION_CONNECT_ERROR);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置认证方式(密码或密钥)
|
||||
*/
|
||||
private void configureAuthentication(JSch jsch, MachineInfoDO machineInfo) {
|
||||
if (machineInfo.getAuthenticationType() == AuthenticationType.SECRET_KEY.getCode()) {
|
||||
// 密钥认证
|
||||
if (machineInfo.getSecretKeyId() == null) {
|
||||
throw exception(SECRET_KEY_NULL);
|
||||
}
|
||||
String pubKeyContent = secretKeyService.getKeyContent(machineInfo.getSecretKeyId());
|
||||
// 验证秘钥格式
|
||||
if (!pubKeyContent.startsWith("-----BEGIN")) {
|
||||
log.error("无效的密钥格式{}", pubKeyContent);
|
||||
throw exception(INVALID_kEY_FORMAT);
|
||||
}
|
||||
try {
|
||||
// 尝试加载秘钥私钥
|
||||
jsch.addIdentity(
|
||||
machineInfo.getName(),
|
||||
pubKeyContent.getBytes(StandardCharsets.UTF_8),
|
||||
null,
|
||||
null
|
||||
);
|
||||
log.info("密钥加载成功 {}", machineInfo.getHostIp());
|
||||
} catch (JSchException e) {
|
||||
log.error("密钥加载失败: {}", e.getMessage());
|
||||
throw exception(READ_SECRET_CONTENT_ERROR);
|
||||
}
|
||||
} else if (machineInfo.getAuthenticationType() == AuthenticationType.PASSWORD.getCode()) {
|
||||
// 密码认证
|
||||
if (!StringUtils.hasText(machineInfo.getPassword())) {
|
||||
throw exception(PASSWORD_NOT_EXISTS);
|
||||
}
|
||||
} else {
|
||||
log.error("不支持该验证类型:{}", machineInfo.getAuthenticationType());
|
||||
throw exception(NOT_SUPPORT_AUTHENTICATION_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置SSH会话参数(安全增强)
|
||||
*/
|
||||
private void configureSession(Session session, MachineInfoDO machineInfo) {
|
||||
Properties config = new Properties();
|
||||
// 根据认证类型配置不同的认证策略
|
||||
if (machineInfo.getAuthenticationType() == 1) { // 密码认证
|
||||
// 设置密码
|
||||
session.setPassword(machineInfo.getPassword());
|
||||
config.put("StrictHostKeyChecking", "no");
|
||||
// 仅使用密码认证(禁用其他认证方式)
|
||||
config.put("PreferredAuthentications", "password");
|
||||
// 禁用公钥相关配置(避免干扰)
|
||||
config.put("PubkeyAuthentication", "no");
|
||||
} else { // 密钥认证
|
||||
try {
|
||||
String preKeyPath = secretKeyService.getSecretKey(machineInfo.getSecretKeyId()).getPath();
|
||||
JSch jsch = new JSch();
|
||||
jsch.addIdentity(preKeyPath); // 添加私钥
|
||||
// 保持默认认证顺序(公钥优先)
|
||||
config.put("PreferredAuthentications", "publicKey,password,keyboard-interactive");
|
||||
} catch (JSchException e) {
|
||||
log.error("SSH密钥配置失败", e);
|
||||
throw exception(SSH_KEY_CONFIGURATION_FAIL);
|
||||
}
|
||||
}
|
||||
config.put("ServerAliveInterval", "30"); // 每30秒发送一次心跳
|
||||
config.put("ServerAliveCountMax", "3"); // 允许3次心跳失败
|
||||
session.setConfig(config);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package cd.casic.module.machine.component;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Component("machineWebSocketSessionManger")
|
||||
//管理webSocketSession
|
||||
public class WebSocketSessionManager {
|
||||
|
||||
//webSocketSessionId - WebSocketSession 保存 WebSocketSession 对象与会话 ID 的映射
|
||||
private static final ConcurrentHashMap<String, WebSocketSession> WebSocketSessionMap = new ConcurrentHashMap<>();
|
||||
|
||||
//webSocketSessionId - WebSocketConnection 与远程机器的会话管理
|
||||
private static final ConcurrentHashMap<String, WebSocketConnection> sessionConnectionMap = new ConcurrentHashMap<>();
|
||||
|
||||
//机器id - WebSocketConnection
|
||||
private static final ConcurrentHashMap<Long, WebSocketConnection> webSocketSessionConnectionMap = new ConcurrentHashMap<>();
|
||||
|
||||
public static void addWebSocketSession(String sessionId, WebSocketSession session) {
|
||||
WebSocketSessionMap.put(sessionId, session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 WebSocketSession
|
||||
*/
|
||||
public static WebSocketSession getWebSocketSession(String sessionId) {
|
||||
return WebSocketSessionMap.get(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 WebSocketSession
|
||||
*/
|
||||
public static void removeWebSocketSession(String sessionId) {
|
||||
WebSocketSessionMap.remove(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 sessionId 是否存在
|
||||
*/
|
||||
public static boolean containsWebSocketSession(String sessionId) {
|
||||
return WebSocketSessionMap.containsKey(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有 WebSocketSession
|
||||
*/
|
||||
public static Collection<WebSocketSession> getAllWebSocketSessions() {
|
||||
return WebSocketSessionMap.values();
|
||||
}
|
||||
|
||||
public static void addWebSocketConnection(String sessionId, WebSocketConnection connection) {
|
||||
sessionConnectionMap.put(sessionId, connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 WebSocketConnection
|
||||
*/
|
||||
public static WebSocketConnection getWebSocketConnection(String sessionId) {
|
||||
return sessionConnectionMap.get(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 WebSocketConnection
|
||||
*/
|
||||
public static void removeWebSocketConnection(String sessionId) {
|
||||
sessionConnectionMap.remove(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 sessionId 是否存在
|
||||
*/
|
||||
public static boolean containsWebSocketConnection(String sessionId) {
|
||||
return sessionConnectionMap.containsKey(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有 WebSocketConnection
|
||||
*/
|
||||
public static ConcurrentHashMap<Long, WebSocketConnection> getAllWebSocketConnections() {
|
||||
return webSocketSessionConnectionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 WebSocketConnection
|
||||
*/
|
||||
public static void addWebSocketConnectionByMachineId(Long machineId, WebSocketConnection connection) {
|
||||
webSocketSessionConnectionMap.put(machineId, connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 WebSocketConnection
|
||||
*/
|
||||
public static WebSocketConnection getWebSocketConnectionByMachineId(Long machineId) {
|
||||
return webSocketSessionConnectionMap.get(machineId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 WebSocketConnection
|
||||
*/
|
||||
public static void removeWebSocketConnectionByMachineId(Long machineId) {
|
||||
webSocketSessionConnectionMap.remove(machineId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 machineId 是否存在
|
||||
*/
|
||||
public static boolean containsMachineId(Long machineId) {
|
||||
return webSocketSessionConnectionMap.containsKey(machineId);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package cd.casic.module.machine.configuration;
|
||||
|
||||
import cd.casic.module.infra.framework.file.core.client.s3.S3FileClientConfig;
|
||||
import cd.casic.module.machine.utils.AliYunOssClient;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class AliYunOssConfig extends S3FileClientConfig{
|
||||
@Value("${aliyun.oss.endpoint}")
|
||||
private String endpoint;
|
||||
@Value("${aliyun.oss.accessKeyId}")
|
||||
private String accessKey;
|
||||
@Value("${aliyun.oss.accessKeySecret}")
|
||||
private String accessSecret;
|
||||
@Value("${aliyun.oss.bucketName}")
|
||||
private String bucket;
|
||||
// 定义 S3 客户端 Bean
|
||||
@Bean
|
||||
public AliYunOssClient aliYunClient() {
|
||||
// 创建配置对象
|
||||
S3FileClientConfig config = new AliYunOssConfig();
|
||||
config.setEndpoint(endpoint);
|
||||
config.setAccessKey(accessKey);
|
||||
config.setAccessSecret(accessSecret);
|
||||
config.setBucket(bucket);
|
||||
AliYunOssClient aliYunOssClient = new AliYunOssClient(1L, config);
|
||||
// 创建并返回客户端实例
|
||||
aliYunOssClient.init();
|
||||
return aliYunOssClient;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package cd.casic.module.machine.configuration;
|
||||
|
||||
import cd.casic.module.machine.Interceptor.WebSocketHandshakeInterceptor;
|
||||
import cd.casic.module.machine.handler.MachineWebSocketHandler;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
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 org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
//WebSocket端点配置
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
@Resource
|
||||
private MachineWebSocketHandler machineWebSocketHandler;
|
||||
|
||||
@Resource
|
||||
private WebSocketHandshakeInterceptor webSocketHandshakeInterceptor;
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(machineWebSocketHandler, "/ssh/terminal")
|
||||
.addInterceptors(webSocketHandshakeInterceptor)
|
||||
.setAllowedOrigins("*"); // 允许跨域(生产环境需限制)
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServletServerContainerFactoryBean createWebSocketContainer() {
|
||||
return new ServletServerContainerFactoryBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServerEndpointExporter serverEndpointExporter() {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package cd.casic.module.machine.contants;
|
||||
|
||||
import cd.casic.framework.commons.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
* 机器报错
|
||||
*/
|
||||
public interface MachineErrorCodeConstants {
|
||||
// ========== 机器基础信息模块 1-003-000-000 ==========
|
||||
ErrorCode MACHINE_INFO_NULL = new ErrorCode(1_003_000_000, "机器信息为空");
|
||||
ErrorCode MACHINE_INFO_HOST_IP_NULL = new ErrorCode(1_003_000_001, "机器主机IP为空");
|
||||
ErrorCode MACHINE_INFO_USER_NAME_NULL = new ErrorCode(1_003_000_002, "机器用户名为空");
|
||||
ErrorCode MACHINE_INFO_TYPE_NULL = new ErrorCode(1_003_000_003, "机器类型为空");
|
||||
ErrorCode MACHINE_INFO_TYPE_NOT_EXISTS = new ErrorCode(1_003_000_004, "机器类型不存在");
|
||||
ErrorCode MACHINE_INFO_TAG_NULL = new ErrorCode(1_003_000_005, "机器唯一标识为空");
|
||||
ErrorCode MACHINE_INFO_TAG_EXISTS = new ErrorCode(1_003_000_006, "机器唯一标识已存在");
|
||||
ErrorCode MACHINE_INFO_AUTHENTICATION_TYPE_NULL = new ErrorCode(1_003_000_007, "机器认证类型为空");
|
||||
ErrorCode MACHINE_INFO_AUTHENTICATION_TYPE_NOT_EXISTS = new ErrorCode(1_003_000_008, "机器认证类型不存在");
|
||||
ErrorCode MACHINE_ENABLE = new ErrorCode(1_003_000_009, "机器启用中");
|
||||
ErrorCode MACHINE_UN_ENABLE = new ErrorCode(1_003_000_010, "机器不可用");
|
||||
|
||||
// ========== 文件操作模块 1-003-001-000 ==========
|
||||
ErrorCode UPLOADING_FILE_FAIL = new ErrorCode(1_003_001_000, "上传文件失败");
|
||||
ErrorCode DOWNLOAD_FILE_FAIL = new ErrorCode(1_003_001_001, "下载失败");
|
||||
ErrorCode FILENAME_NULL = new ErrorCode(1_003_001_002, "文件名为空");
|
||||
ErrorCode READ_FILE_FAIL = new ErrorCode(1_003_001_003, "读取文件失败");
|
||||
ErrorCode DELETE_FILE_FAIL = new ErrorCode(1_003_001_004, "删除文件失败");
|
||||
|
||||
// ========== 机器环境变量模块 1-003-002-000 ==========
|
||||
ErrorCode MACHINE_ENV_NULL = new ErrorCode(1_003_002_000, "机器环境变量为空");
|
||||
ErrorCode MACHINE_ENV_NOT_EXISTS = new ErrorCode(1_003_002_001, "机器不存在");
|
||||
ErrorCode MACHINE_ENV_KEY_ILLEGAL = new ErrorCode(1_003_002_002, "机器环境变量键不合法");
|
||||
|
||||
// ========== 机器代理模块 1-003-003-000 ==========
|
||||
ErrorCode MACHINE_PROXY_HOST_IP_NULL = new ErrorCode(1_003_003_000, "机器代理主机地址为空");
|
||||
ErrorCode MACHINE_PROXY_USER_NAME_NULL = new ErrorCode(1_003_003_001, "机器代理用户名为空");
|
||||
ErrorCode MACHINE_PROXY_NOT_EXISTS = new ErrorCode(1_003_003_002, "机器代理不存在");
|
||||
ErrorCode MACHINE_PROXY_TYPE_NOT_EXISTS = new ErrorCode(1_003_003_003, "机器代理类型不存在");
|
||||
ErrorCode MACHINE_PROXY_IS_ONLINE = new ErrorCode(1_003_003_004, "机器代理在线,不能删除");
|
||||
|
||||
// ========== 密钥模块 1-003-004-000 ==========
|
||||
ErrorCode SECRET_KEY_NULL = new ErrorCode(1_003_004_000, "密钥为空");
|
||||
ErrorCode SECRET_KEY_NOT_EXISTS = new ErrorCode(1_003_004_001, "密钥不存在");
|
||||
ErrorCode SECRET_KEY_PATH_NULL = new ErrorCode(1_003_004_002, "密钥路径为空");
|
||||
ErrorCode INVALID_kEY_FORMAT = new ErrorCode(1_003_004_003, "无效的密钥格式");
|
||||
ErrorCode READ_SECRET_CONTENT_ERROR = new ErrorCode(1_003_004_004, "读取密钥加载失败");
|
||||
|
||||
// ========== 其他模块 1-003-005-000 ==========
|
||||
ErrorCode OSS_PARAM_NULL = new ErrorCode(1_003_005_000, "oss参数无法读取");
|
||||
ErrorCode FILE_UPLOAD_ERROR = new ErrorCode(1_003_005_001, "文件上传失败");
|
||||
ErrorCode FILE_DOWNLOAD_ERROR = new ErrorCode(1_003_005_002, "文件下载失败");
|
||||
|
||||
//========== 会话连接模块 1-003-006-000 ==========
|
||||
ErrorCode SESSION_CONNECT_ERROR = new ErrorCode(1_003_006_001, "会话连接失败");
|
||||
ErrorCode CLOSE_CLOSE_SESSION_ERROR = new ErrorCode(1_003_006_002, "关闭连接失败");
|
||||
ErrorCode SESSION_NOT_CONNECT = new ErrorCode(1_003_006_003, "会话未连接");
|
||||
ErrorCode EXECUTE_COMMAND_FAIL = new ErrorCode(1_003_006_004, "命令执行失败");
|
||||
ErrorCode PASSWORD_NOT_EXISTS = new ErrorCode(1_003_006_005, "密码不存在");
|
||||
ErrorCode SSH_KEY_CONFIGURATION_FAIL = new ErrorCode(1_003_006_006, "SSH密钥配置失败");
|
||||
ErrorCode NOT_SUPPORT_AUTHENTICATION_TYPE = new ErrorCode(1_003_006_007, "认证类型不支持");
|
||||
ErrorCode CREATE_SESSION_ERROR = new ErrorCode(1_003_006_008, "创建会话失败");
|
||||
ErrorCode WEBSOCKET_SEND_MESSAGE_ERROR = new ErrorCode(1_003_006_009, "websocket发送消息失败");
|
||||
ErrorCode CREATE_CHANEL_ERROR = new ErrorCode(1_003_006_010, "执行通道创建失败");
|
||||
ErrorCode CHANEL_CONNECT_FAIL = new ErrorCode(1_003_006_011, "通道连接失败");
|
||||
ErrorCode FAILED_TO_PARSE_URL_PARAMETERS = new ErrorCode(1_003_006_012, "解析URL参数失败");
|
||||
|
||||
//========== 远程文件树模块 1-003-007-000 ==========
|
||||
ErrorCode NOT_DIRECTORY_NODE = new ErrorCode(1_003_007_001, "非目录节点不能添加子节点");
|
||||
ErrorCode READ_REMOTE_DIRECTORY_FAIL = new ErrorCode(1_003_007_002, "读取远程文件目录结构失败");
|
||||
ErrorCode CONNECTION_LOST = new ErrorCode(1_003_007_003, "SFTP连接无效,无法列出文件");
|
||||
ErrorCode PATH_NOT_EXISTS = new ErrorCode(1_003_007_004, "路径不存在");
|
||||
ErrorCode NO_PATH_PERMISSION = new ErrorCode(1_003_007_005, "无路径访问权限");
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package cd.casic.module.machine.controller;
|
||||
|
||||
import cd.casic.framework.commons.pojo.CommonResult;
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.framework.commons.util.object.BeanUtils;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineEnvDO;
|
||||
import cd.casic.module.machine.service.MachineEnvService;
|
||||
import cd.casic.module.machine.controller.vo.MachineEnvVO;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import static cd.casic.framework.commons.pojo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* 环境变量控制器
|
||||
*/
|
||||
@Tag(name = "环境变量管理")
|
||||
@RestController
|
||||
@RequestMapping("/ci/machineEnv")
|
||||
@Validated
|
||||
public class MachineEnvController {
|
||||
|
||||
@Resource
|
||||
private MachineEnvService machineEnvService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "新增环境变量")
|
||||
@PreAuthorize("@ss.hasPermission('ci:machineEnv:create')")
|
||||
public CommonResult<Long> createEnv(@Valid @RequestBody MachineEnvVO machineEnvVO) {
|
||||
Long id = machineEnvService.createEnv(machineEnvVO);
|
||||
return success(id);
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改环境变量")
|
||||
@PreAuthorize("@ss.hasPermission('ci:machineEnv:update')")
|
||||
public CommonResult<Boolean> updateEnv(@Valid @RequestBody MachineEnvVO machineEnvVO) {
|
||||
machineEnvService.updateEnv(machineEnvVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除机器的环境变量")
|
||||
@PreAuthorize("@ss.hasPermission('ci:machineEnv:delete')")
|
||||
public CommonResult<Boolean> deleteEnv(@RequestParam("id") Long id) {
|
||||
machineEnvService.deleteEnv(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/deleteList")
|
||||
@Operation(summary = "批量删除机器环境变量")
|
||||
@PreAuthorize("@ss.hasPermission('ci:machineEnv:delete')")
|
||||
public CommonResult<Boolean> deleteEnvList(@RequestParam("ids") String ids) {
|
||||
machineEnvService.deleteEnvList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/getEnv")
|
||||
@Operation(summary = "获取机器的环境变量")
|
||||
public CommonResult<MachineEnvVO> getEnv(@RequestParam("id") Long id) {
|
||||
MachineEnvVO machineEnvVO = machineEnvService.getEnv(id);
|
||||
return success(machineEnvVO);
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/list")
|
||||
@Operation(summary = "获取环境变量列表")
|
||||
public CommonResult<PageResult<MachineEnvVO>> getEnvPage(@Valid @RequestBody MachineEnvVO machineEnvVO) {
|
||||
PageResult<MachineEnvDO> pageResult = machineEnvService.getEnvPage(machineEnvVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
return success(new PageResult<>(pageResult.getTotal()));
|
||||
}
|
||||
return success(BeanUtils.toBean(pageResult, MachineEnvVO.class));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,131 @@
|
||||
package cd.casic.module.machine.controller;
|
||||
|
||||
import cd.casic.framework.commons.pojo.CommonResult;
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.framework.commons.util.object.BeanUtils;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
|
||||
import cd.casic.module.machine.enums.ConnectionStatus;
|
||||
import cd.casic.module.machine.service.MachineInfoService;
|
||||
import cd.casic.module.machine.controller.vo.MachineInfoVO;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static cd.casic.framework.commons.pojo.CommonResult.success;
|
||||
|
||||
@RestController
|
||||
@Tag(name = "机器信息管理")
|
||||
@RequestMapping("/ci/machineInfo")
|
||||
@Validated
|
||||
public class MachineInfoController {
|
||||
@Resource
|
||||
private MachineInfoService machineInfoService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "新增机器信息")
|
||||
// @PreAuthorize("@ss.hasPermission('ci:machineInfo:create')")
|
||||
public CommonResult<Long> createMachine(@Valid @RequestBody MachineInfoVO machineInfoVO) {
|
||||
Long id = machineInfoService.createMachine(machineInfoVO);
|
||||
return success(id);
|
||||
}
|
||||
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "编辑机器信息")
|
||||
// @PreAuthorize("@ss.hasPermission('ci:machineInfo:update')")
|
||||
public CommonResult<Boolean> updateMachineInfo(@Valid @RequestBody MachineInfoVO machineInfoVO) {
|
||||
machineInfoService.updateMachineInfo(machineInfoVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/updateStatus")
|
||||
@Operation(summary = "机器启用/停用")
|
||||
// @PreAuthorize("@ss.hasPermission('ci:machineInfo:status')")
|
||||
public CommonResult<Integer> updateStatus(@Valid @RequestBody MachineInfoVO machineInfoVO) {
|
||||
Integer newStatus = machineInfoService.updateStatus(machineInfoVO);
|
||||
return success(newStatus);
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@Operation(summary = "获取机器信息列表")
|
||||
public CommonResult<PageResult<MachineInfoVO>> list(@Valid @RequestBody MachineInfoVO machineInfoVO) {
|
||||
PageResult<MachineInfoDO> pageResult = machineInfoService.listMachineInfo(machineInfoVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
return success(new PageResult<>(pageResult.getTotal()));
|
||||
}
|
||||
return success(BeanUtils.toBean(pageResult, MachineInfoVO.class));
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "机器信息删除")
|
||||
// @PreAuthorize("@ss.hasPermission('ci:machineInfo:delete')")
|
||||
public CommonResult<Boolean> deleteMachineInfo(@RequestParam("machineInfoId") Long machineInfoId) {
|
||||
machineInfoService.deleteMachineInfo(machineInfoId);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/deleteList")
|
||||
@Operation(summary = "批量删除机器信息")
|
||||
// @PreAuthorize("@ss.hasPermission('ci:machineInfo:delete')")
|
||||
public CommonResult<Boolean> deleteMachineInfoList(@RequestParam("machineInfoIds") String ids) {
|
||||
machineInfoService.deleteMachineInfoList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/test")
|
||||
@Operation(summary = "测试机器连接")
|
||||
public CommonResult<Boolean> testConnection(@RequestParam("id") Long id) {
|
||||
return success(machineInfoService.testConnection(id));
|
||||
}
|
||||
|
||||
@GetMapping("/status")
|
||||
@Operation(summary = "获取机器连接状态")
|
||||
public CommonResult<ConnectionStatus> getConnectionStatus(@RequestParam Long id) {
|
||||
return success(machineInfoService.getConnectionStatus(id));
|
||||
}
|
||||
|
||||
@GetMapping("/status/all")
|
||||
@Operation(summary = "获取所有机器连接状态")
|
||||
public CommonResult<Map<Long, ConnectionStatus>> getAllConnectionStatus() {
|
||||
return success(machineInfoService.getAllConnectionStatus());
|
||||
}
|
||||
|
||||
@GetMapping("/connect")
|
||||
@Operation(summary = "建立连接")
|
||||
public CommonResult<Map<String, Object>> connect(@RequestParam Long id) {
|
||||
return success(machineInfoService.connect(id));
|
||||
}
|
||||
|
||||
@GetMapping("/fileTreeNode")
|
||||
@Operation(summary = "获得文件树")
|
||||
public CommonResult<Map<String, Object>> fileTreeNode(
|
||||
@RequestParam Long machineId,
|
||||
@RequestParam(required = false, defaultValue = "/") String path
|
||||
) {
|
||||
return CommonResult.success(machineInfoService.fileTreeNode(machineId, path));
|
||||
}
|
||||
// @GetMapping("/upload")
|
||||
// @Operation(summary = "上传文件到远程机器")
|
||||
// public CommonResult<Boolean> uploadFile(
|
||||
// @RequestParam String sessionId,
|
||||
// @RequestParam String localFilePath,
|
||||
// @RequestParam String remoteFilePath
|
||||
// ) {
|
||||
// return success(machineInfoService.uploadFile(sessionId, localFilePath, remoteFilePath));
|
||||
// }
|
||||
//
|
||||
// @GetMapping("/download")
|
||||
// @Operation(summary = "从远程机器下载文件")
|
||||
// public CommonResult<Boolean> downloadFile(
|
||||
// @RequestParam String sessionId,
|
||||
// @RequestParam String remoteFilePath,
|
||||
// @RequestParam String localFilePath) {
|
||||
// return success(machineInfoService.downloadFile(sessionId, remoteFilePath, localFilePath));
|
||||
// }
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package cd.casic.module.machine.controller;
|
||||
|
||||
import cd.casic.framework.commons.pojo.CommonResult;
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.framework.commons.util.object.BeanUtils;
|
||||
import cd.casic.module.machine.controller.vo.MachineProxyVO;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineProxyDO;
|
||||
import cd.casic.module.machine.service.MachineProxyService;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static cd.casic.framework.commons.pojo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* 机器代理控制器
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/ci/machineProxy")
|
||||
@Tag(name = "机器代理管理")
|
||||
@Validated
|
||||
public class MachineProxyController {
|
||||
|
||||
@Resource
|
||||
private MachineProxyService machineProxyService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "注册新的机器代理")
|
||||
// @PreAuthorize("@ss.hasPermission('ci:machineProxy:create')")
|
||||
public CommonResult<Long> createProxy(@Valid @RequestBody MachineProxyVO machineProxyVO) {
|
||||
Long id = machineProxyService.createProxy(machineProxyVO);
|
||||
return success(id);
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "修改代理")
|
||||
// @PreAuthorize("@ss.hasPermission('ci:machineProxy:update')")
|
||||
public CommonResult<Boolean> updateProxy(@Valid @RequestBody MachineProxyVO machineProxyVO) {
|
||||
machineProxyService.updateProxy(machineProxyVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@Operation(summary = "获取代理列表")
|
||||
public CommonResult<PageResult<MachineProxyVO>> getProxyPage(@Valid @RequestBody MachineProxyVO machineProxyVO) {
|
||||
PageResult<MachineProxyDO> pageResult = machineProxyService.getProxyPage(machineProxyVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
return success(new PageResult<>(pageResult.getTotal()));
|
||||
}
|
||||
return success(BeanUtils.toBean(pageResult, MachineProxyVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/allStatus")
|
||||
@Operation(summary = "获取所有代理的状态统计")
|
||||
public CommonResult<Map<Integer, Long>> getStatusStatistics() {
|
||||
return success(machineProxyService.getAllProxyStatus());
|
||||
}
|
||||
|
||||
@DeleteMapping("delete")
|
||||
@Operation(summary = "删除代理")
|
||||
public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
|
||||
machineProxyService.delete(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/deleteList")
|
||||
@Operation(summary = "批量删除代理")
|
||||
// @PreAuthorize("@ss.hasPermission('ci:machineProxy:delete')")
|
||||
public CommonResult<Boolean> deleteProxyList(@RequestParam String ids) {
|
||||
machineProxyService.deleteProxyList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package cd.casic.module.machine.controller;
|
||||
|
||||
import cd.casic.framework.commons.pojo.CommonResult;
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.framework.commons.util.object.BeanUtils;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
|
||||
import cd.casic.module.machine.dal.dataobject.SecretKeyDO;
|
||||
import cd.casic.module.machine.service.SecretKeyService;
|
||||
import cd.casic.module.machine.controller.vo.SecretKeyVO;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static cd.casic.framework.commons.pojo.CommonResult.success;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/ci/secretKey")
|
||||
@Tag(name = "密钥管理")
|
||||
@Validated
|
||||
public class SecretKeyController {
|
||||
@Resource
|
||||
private SecretKeyService secretKeyService;
|
||||
|
||||
@PostMapping(value = "/create")
|
||||
@Operation(summary = "新增密钥")
|
||||
// @PreAuthorize("@ss.hasPermission('ci:secretKey:create')")
|
||||
public CommonResult<Long> createSecretKey(@Valid @RequestBody SecretKeyVO secretKeyVO) throws Exception {
|
||||
Long secretKeyId = secretKeyService.createSecretKey(secretKeyVO);
|
||||
return success(secretKeyId);
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "编辑密钥信息")
|
||||
// @PreAuthorize("@ss.hasPermission('ci:secretKey:update')")
|
||||
public CommonResult<Boolean> updateSecretKey(@Valid @RequestBody SecretKeyVO secretKeyVO) {
|
||||
secretKeyService.updateSecretKey(secretKeyVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/bindingMachine")
|
||||
@Operation(summary = "绑定机器")
|
||||
// @PreAuthorize("@ss.hasPermission('ci:secretKey:binding')")
|
||||
public CommonResult<Boolean> bindingMachine(@Valid @RequestBody SecretKeyVO secretKeyVO) {
|
||||
secretKeyService.bindingMachine(secretKeyVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PutMapping("/unbindMachine")
|
||||
@Operation(summary = "解绑机器")
|
||||
public CommonResult<Boolean> unbindMachine(@Valid @RequestBody SecretKeyVO secretKeyVO) {
|
||||
secretKeyService.unbindMachine(secretKeyVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/getBindMachine")
|
||||
@Operation(summary = "获取密钥绑定的机器列表")
|
||||
public CommonResult<List<MachineInfoDO>> getBindMachine(@RequestParam Long secretKeyId) {
|
||||
return success(secretKeyService.getBindMachine(secretKeyId));
|
||||
}
|
||||
|
||||
@GetMapping("/getSecretKey")
|
||||
@Operation(summary = "获取机器的密钥")
|
||||
public CommonResult<SecretKeyVO> getSecretKey(@RequestParam("id") Long id) {
|
||||
SecretKeyVO secretKeyVO = secretKeyService.getSecretKey(id);
|
||||
return success(secretKeyVO);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "密钥信息删除")
|
||||
public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
|
||||
secretKeyService.deleteSecretKey(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/deleteList")
|
||||
@Operation(summary = "批量删除密钥")
|
||||
// @PreAuthorize("@ss.hasPermission('ci:secretKey:delete')")
|
||||
public CommonResult<Boolean> deleteSecretKeyList(@RequestParam("ids") List<Long> ids) {
|
||||
secretKeyService.deleteSecretKeyList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/list")
|
||||
@Operation(summary = "获取密钥信息列表")
|
||||
public CommonResult<PageResult<SecretKeyVO>> getSecretKeyPage(@Valid @RequestBody SecretKeyVO secretKeyVO) {
|
||||
PageResult<SecretKeyDO> pageResult = secretKeyService.getSecretKeypage(secretKeyVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
return success(new PageResult<>(pageResult.getTotal()));
|
||||
}
|
||||
return success(BeanUtils.toBean(pageResult, SecretKeyVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/download")
|
||||
@Operation(summary = "下载密钥文件")
|
||||
public ResponseEntity<InputStreamResource> downloadSecretFile(@RequestParam("id") Long id) {
|
||||
return secretKeyService.downloadSecretFile(id);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package cd.casic.module.machine.controller.vo;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "管理后台 - 机器环境变量信息 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true) // 添加链式调用支持
|
||||
public class MachineEnvVO extends PageParam {
|
||||
|
||||
@Schema(description = "环境变量ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "环境变量键", requiredMode = Schema.RequiredMode.REQUIRED, example = "JAVA_HOME")
|
||||
private String envKey;
|
||||
|
||||
@Schema(description = "环境变量值", requiredMode = Schema.RequiredMode.REQUIRED, example = "/usr/java/jdk1.8.0_271")
|
||||
private String envValue;
|
||||
|
||||
@Schema(description = "环境变量描述", example = "Java运行环境路径")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "关联的机器ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long machineId;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-06-15T10:30:00")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间", example = "2023-06-15T10:30:00")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package cd.casic.module.machine.controller.vo;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "管理后台 - 机器信息 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class MachineInfoVO extends PageParam {
|
||||
|
||||
@Schema(description = "机器ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-06-15T10:30:00")
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description = "更新时间", example = "2023-06-15T10:30:00")
|
||||
private Date updateTime;
|
||||
|
||||
@Schema(description = "机器名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "server-01")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "机器标签,唯一标识", example = "production,web-server")
|
||||
private String tag;
|
||||
|
||||
@Schema(description = "主机IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "192.168.1.100")
|
||||
private String hostIp;
|
||||
|
||||
@Schema(description = "机器描述", example = "生产环境Web服务器")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "登录用户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "admin")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "机器状态", example = "online,offline,maintenance")
|
||||
private Integer status = -1;
|
||||
|
||||
@Schema(description = "SSH端口", example = "22")
|
||||
private Integer sshPort;
|
||||
|
||||
@Schema(description = "登录密码", example = "******")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "密钥ID", example = "5")
|
||||
private Long secretKeyId;
|
||||
|
||||
@Schema(description = "代理ID", example = "101")
|
||||
private Long machineProxyId;
|
||||
|
||||
@Schema(description = "认证类型", example = "password,key")
|
||||
private Integer authenticationType ;
|
||||
|
||||
@Schema(description = "机器信息类型", example = "Linux,Windows")
|
||||
private Integer machineInfoType;
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package cd.casic.module.machine.controller.vo;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 机器代理信息 Response VO")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class MachineProxyVO extends PageParam {
|
||||
|
||||
@Schema(description = "代理ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "admin")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "代理类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "HTTP")
|
||||
private Integer proxyType = -1;
|
||||
|
||||
@Schema(description = "版本号", example = "1.0.0")
|
||||
private String version;
|
||||
|
||||
@Schema(description = "状态(ONLINE:在线,OFFLINE:离线)", requiredMode = Schema.RequiredMode.REQUIRED, example = "ONLINE")
|
||||
private int status = -1;
|
||||
|
||||
@Schema(description = "描述信息", example = "用于生产环境的代理服务器")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "主机IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "192.168.1.100")
|
||||
private String hostIp;
|
||||
|
||||
@Schema(description = "SSH端口", requiredMode = Schema.RequiredMode.REQUIRED, example = "22")
|
||||
private String sshPort;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-06-15T10:30:00")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间", example = "2023-06-15T10:30:00")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "密码", example = "******")
|
||||
private String password;
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package cd.casic.module.machine.controller.vo;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "管理后台 - 密钥信息 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true) // 添加链式调用支持
|
||||
public class SecretKeyVO extends PageParam {
|
||||
|
||||
@Schema(description = "密钥ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "密钥名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "生产环境密钥")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "密钥描述", example = "用于加密敏感数据的密钥")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "存储路径(本地上传文件路径)", example = "/data/secret_keys/")
|
||||
private String path;
|
||||
|
||||
@Schema(description = "文件名", example = "key.pem")
|
||||
private String fileName;
|
||||
|
||||
@Schema(description = "密钥密码", example = "******")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-06-15T10:30:00")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间", example = "2023-06-15T10:30:00")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "关联的机器ID列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1024, 2048]")
|
||||
private List<Long> machineInfoIds;
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package cd.casic.module.machine.dal.dataobject;
|
||||
|
||||
|
||||
import cd.casic.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 环境变量实体类
|
||||
*/
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("machine_env")
|
||||
public class MachineEnvDO extends BaseDO {
|
||||
|
||||
|
||||
/**
|
||||
* 环境变量id
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 机器ID(唯一关联)
|
||||
*/
|
||||
private Long machineId;
|
||||
|
||||
/**
|
||||
* 环境变量键
|
||||
*/
|
||||
private String envKey;
|
||||
|
||||
/**
|
||||
* 环境变量值
|
||||
*/
|
||||
private String envValue;
|
||||
|
||||
|
||||
/**
|
||||
* 描述信息
|
||||
*/
|
||||
private String description;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,68 @@
|
||||
package cd.casic.module.machine.dal.dataobject;
|
||||
|
||||
import cd.casic.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@TableName(value = "machine_info")
|
||||
public class MachineInfoDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 机器id
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
|
||||
@TableField(value = "name")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 机器唯一标识
|
||||
*/
|
||||
@TableField(value = "tag")
|
||||
private String tag;
|
||||
|
||||
@TableField(value = "host_ip")
|
||||
private String hostIp;
|
||||
|
||||
@TableField(value = "description")
|
||||
private String description;
|
||||
|
||||
@TableField(value = "machine_info_type")
|
||||
private Integer machineInfoType;
|
||||
|
||||
@TableField(value = "status")
|
||||
private Integer status;
|
||||
|
||||
//用户名
|
||||
@TableField(value = "username")
|
||||
private String username;
|
||||
|
||||
//SSH端口号
|
||||
@TableField(value = "ssh_port")
|
||||
private Integer sshPort;
|
||||
|
||||
@TableField(value = "password")
|
||||
private String password;
|
||||
|
||||
@TableField(value = "secret_key_id")
|
||||
private Long secretKeyId;
|
||||
|
||||
@TableField(value = "machine_proxy_id")
|
||||
private Long machineProxyId;
|
||||
|
||||
@TableField(value = "authentication_type")
|
||||
private Integer authenticationType;
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package cd.casic.module.machine.dal.dataobject;
|
||||
|
||||
import cd.casic.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cd.casic.module.machine.enums.MachineProxyStatus;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import cd.casic.module.machine.enums.MachineProxyType;
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 机器代理实体类
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TableName("machine_proxy")
|
||||
public class MachineProxyDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 代理id
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
@TableField(value = "host_ip")
|
||||
private String hostIp;
|
||||
|
||||
@TableField(value = "ssh_port")
|
||||
private String sshPort;
|
||||
|
||||
//todo 字典??
|
||||
@TableField(value = "proxy_type")
|
||||
private int proxyType;
|
||||
|
||||
@TableField(value = "version")
|
||||
private String version;
|
||||
|
||||
//todo 字典??
|
||||
@TableField(value = "status")
|
||||
private int status;
|
||||
|
||||
@TableField(value = "username")
|
||||
private String username;
|
||||
|
||||
@TableField(value = "password")
|
||||
private String password;
|
||||
|
||||
@TableField(value = "description")
|
||||
private String description;
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package cd.casic.module.machine.dal.dataobject;
|
||||
|
||||
import cd.casic.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@TableName(value = "machine_secret_key")
|
||||
public class SecretKeyDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 密钥id
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
@TableField(value = "name")
|
||||
private String name;
|
||||
|
||||
@TableField(value = "description")
|
||||
private String description;
|
||||
|
||||
//oss存储路径
|
||||
@TableField(value = "path")
|
||||
private String path;
|
||||
|
||||
@TableField
|
||||
private String fileName;
|
||||
|
||||
//密钥密码
|
||||
@TableField(value = "password")
|
||||
private String password;
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package cd.casic.module.machine.dal.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
//文件节点类
|
||||
@Data
|
||||
/*
|
||||
* 远程文件系统的节点信息,用于构建文件树结构
|
||||
*/
|
||||
@Schema(description = "远程文件系统节点")
|
||||
public class FileNode {
|
||||
@Schema(description = "文件名或目录名")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "是否为目录")
|
||||
private boolean isDirectory;
|
||||
|
||||
@Schema(description = "文件大小(字节)")
|
||||
private long size;
|
||||
|
||||
@Schema(description = "文件权限字符串(如 rwxr-xr-x)")
|
||||
private String permissions;
|
||||
|
||||
@Schema(description = "最后修改时间(时间戳,毫秒)")
|
||||
private long modifiedTime;
|
||||
|
||||
@Schema(description = "子节点列表(仅目录有此属性)")
|
||||
private List<FileNode> children;
|
||||
|
||||
public FileNode(String name, boolean isDirectory, long size, String permissions, long modifiedTime) {
|
||||
this.name = name;
|
||||
this.isDirectory = isDirectory;
|
||||
this.size = size;
|
||||
this.permissions = permissions;
|
||||
this.modifiedTime = modifiedTime;
|
||||
|
||||
if (isDirectory) {
|
||||
this.children = new ArrayList<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void addChild(FileNode child) {
|
||||
if (this.children == null) {
|
||||
throw exception(NOT_DIRECTORY_NODE);
|
||||
}
|
||||
this.children.add(child);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package cd.casic.module.machine.dal.mysql;
|
||||
|
||||
import cd.casic.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cd.casic.module.machine.controller.vo.MachineEnvVO;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineEnvDO;
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 环境变量Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface MachineEnvMapper extends BaseMapperX<MachineEnvDO> {
|
||||
default PageResult<MachineEnvDO> selectPage(MachineEnvVO machineEnvVO) {
|
||||
LambdaQueryWrapperX<MachineEnvDO> machineEnvDOLambdaQueryWrapperX = new LambdaQueryWrapperX<MachineEnvDO>()
|
||||
.likeIfPresent(MachineEnvDO::getEnvKey, machineEnvVO.getEnvKey())
|
||||
.likeIfPresent(MachineEnvDO::getDescription, machineEnvVO.getDescription());
|
||||
if (machineEnvVO.getMachineId() != null && machineEnvVO.getMachineId() > 0) {
|
||||
machineEnvDOLambdaQueryWrapperX.eqIfPresent(MachineEnvDO::getMachineId, machineEnvVO.getMachineId());
|
||||
}
|
||||
return selectPage(machineEnvVO, machineEnvDOLambdaQueryWrapperX);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package cd.casic.module.machine.dal.mysql;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cd.casic.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cd.casic.module.machine.controller.vo.MachineEnvVO;
|
||||
import cd.casic.module.machine.controller.vo.MachineInfoVO;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineEnvDO;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface MachineInfoMapper extends BaseMapperX<MachineInfoDO> {
|
||||
default Boolean existsByTag(String tag) {
|
||||
return selectOne(new QueryWrapper<MachineInfoDO>().eq("tag", tag)) != null;
|
||||
}
|
||||
|
||||
default void updateStatus(Long machineInfoId, Integer status) {
|
||||
UpdateWrapper<MachineInfoDO> set = new UpdateWrapper<>();
|
||||
set.eq("id", machineInfoId).set("status", status);
|
||||
this.update(null, set);
|
||||
}
|
||||
|
||||
default void bindingSecretKey(List<Long> machineInfoIds, Long secretKeyId) {
|
||||
UpdateWrapper<MachineInfoDO> wrapper = new UpdateWrapper<>();
|
||||
wrapper.in("id", machineInfoIds) // 匹配 ID 集合
|
||||
.set("secret_key_id", secretKeyId); // 设置新的 status 值
|
||||
this.update(null, wrapper);
|
||||
}
|
||||
|
||||
default PageResult<MachineInfoDO> selectPage(MachineInfoVO machineInfoVO) {
|
||||
LambdaQueryWrapperX<MachineInfoDO> machineInfoDOLambdaQueryWrapperX = new LambdaQueryWrapperX<MachineInfoDO>()
|
||||
.likeIfPresent(MachineInfoDO::getName, machineInfoVO.getName())
|
||||
.likeIfPresent(MachineInfoDO::getTag, machineInfoVO.getTag())
|
||||
.likeIfPresent(MachineInfoDO::getDescription, machineInfoVO.getDescription())
|
||||
.likeIfPresent(MachineInfoDO::getUsername, machineInfoVO.getUsername())
|
||||
.likeIfPresent(MachineInfoDO::getHostIp, machineInfoVO.getHostIp());
|
||||
if (machineInfoVO.getStatus() != -1) {
|
||||
machineInfoDOLambdaQueryWrapperX.eqIfPresent(MachineInfoDO::getStatus, machineInfoVO.getStatus());
|
||||
}
|
||||
return selectPage(machineInfoVO, machineInfoDOLambdaQueryWrapperX);
|
||||
}
|
||||
|
||||
default List<MachineInfoDO> selectBindMachineBySecretKey(Long secretKeyId) {
|
||||
LambdaQueryWrapperX<MachineInfoDO> lambdaQueryWrapperX = new LambdaQueryWrapperX<MachineInfoDO>()
|
||||
.eq(MachineInfoDO::getSecretKeyId, secretKeyId);
|
||||
return selectList(lambdaQueryWrapperX);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package cd.casic.module.machine.dal.mysql;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cd.casic.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cd.casic.module.machine.controller.vo.MachineProxyVO;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineProxyDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 机器代理Mapper接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface MachineProxyMapper extends BaseMapperX<MachineProxyDO> {
|
||||
|
||||
default PageResult<MachineProxyDO> selectPage(MachineProxyVO machineProxyVO) {
|
||||
LambdaQueryWrapperX<MachineProxyDO> machineProxyDOLambdaQueryWrapperX = new LambdaQueryWrapperX<MachineProxyDO>()
|
||||
.eqIfPresent(MachineProxyDO::getHostIp, machineProxyVO.getHostIp())
|
||||
.likeIfPresent(MachineProxyDO::getDescription, machineProxyVO.getDescription());
|
||||
if (machineProxyVO.getStatus() != -1) {
|
||||
machineProxyDOLambdaQueryWrapperX.eqIfPresent(MachineProxyDO::getStatus, machineProxyVO.getStatus());
|
||||
}
|
||||
if (machineProxyVO.getProxyType() != -1) {
|
||||
machineProxyDOLambdaQueryWrapperX.eqIfPresent(MachineProxyDO::getProxyType, machineProxyVO.getProxyType());
|
||||
}
|
||||
return selectPage(machineProxyVO, machineProxyDOLambdaQueryWrapperX);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package cd.casic.module.machine.dal.mysql;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cd.casic.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cd.casic.module.machine.controller.vo.SecretKeyVO;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
|
||||
import cd.casic.module.machine.dal.dataobject.SecretKeyDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface SecretKeyMapper extends BaseMapperX<SecretKeyDO> {
|
||||
//查询列表
|
||||
default PageResult<SecretKeyDO> selectPage(SecretKeyVO secretKeyVO) {
|
||||
return selectPage(secretKeyVO, new LambdaQueryWrapperX<SecretKeyDO>()
|
||||
.likeIfPresent(SecretKeyDO::getName, secretKeyVO.getName())
|
||||
.likeIfPresent(SecretKeyDO::getDescription, secretKeyVO.getDescription()));
|
||||
}
|
||||
|
||||
default void bindingMachine(Long machineInfoId, List<Long> secretKeyId) {
|
||||
UpdateWrapper<SecretKeyDO> set = new UpdateWrapper<>();
|
||||
set.eq("id", secretKeyId).set("machineInfoId", machineInfoId);
|
||||
this.update(null, set);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package cd.casic.module.machine.enums;
|
||||
|
||||
|
||||
import cd.casic.framework.commons.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum AuthenticationType implements IntArrayValuable {
|
||||
PASSWORD(1,"密码认证"),
|
||||
SECRET_KEY(2,"密钥认证");
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AuthenticationType::getCode).toArray();
|
||||
private final int code;
|
||||
|
||||
private final String message;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package cd.casic.module.machine.enums;
|
||||
|
||||
import cd.casic.framework.commons.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 连接状态枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ConnectionStatus implements IntArrayValuable {
|
||||
DISCONNECTED(1, "断开连接"),
|
||||
CONNECTING(2, "正在连接"),
|
||||
CONNECTED(3, "已连接"),
|
||||
AUTH_FAILED(4, "认证失败"),
|
||||
CONNECTION_TIMEOUT(5, "连接超时"),
|
||||
CONNECTION_ERROR(6, "连接错误"),
|
||||
CLOSED(7, "已关闭");
|
||||
|
||||
private final int code;
|
||||
|
||||
private final String message;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ConnectionStatus::getCode).toArray();
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package cd.casic.module.machine.enums;
|
||||
|
||||
|
||||
import cd.casic.framework.commons.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MachineInfoStatus implements IntArrayValuable {
|
||||
ENABLE(1, "启用"),
|
||||
UN_ENABLE(0, "停用");
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MachineInfoStatus::getCode).toArray();
|
||||
private final int code;
|
||||
|
||||
private final String message;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package cd.casic.module.machine.enums;
|
||||
|
||||
|
||||
import cd.casic.framework.commons.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MachineInfoType implements IntArrayValuable {
|
||||
Linux(1,"Linux"),
|
||||
WINDOWS(2,"Windows");
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MachineInfoType::getCode).toArray();
|
||||
private final int code;
|
||||
|
||||
private final String message;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package cd.casic.module.machine.enums;
|
||||
|
||||
|
||||
import cd.casic.framework.commons.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MachineProxyStatus implements IntArrayValuable {
|
||||
/**
|
||||
* 代理状态 (online, offline, installing, updating, error)
|
||||
*/
|
||||
ONLINE(1, "online"),
|
||||
OFFLINE(2, "offline"),
|
||||
INSTALLING(3, "installing"),
|
||||
UPDATING(4, "updating"),
|
||||
ERROR(5, "error");
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MachineProxyStatus::getCode).toArray();
|
||||
|
||||
private final int code;
|
||||
|
||||
private final String message;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package cd.casic.module.machine.enums;
|
||||
|
||||
import cd.casic.framework.commons.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MachineProxyType implements IntArrayValuable {
|
||||
HTTP(1, "http"),
|
||||
SOCKS4(2, "socks4"),
|
||||
SOCKS5(3, "socks5");
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MachineProxyType::getCode).toArray();
|
||||
|
||||
/**
|
||||
* 状态值
|
||||
*/
|
||||
private final Integer code;
|
||||
/**
|
||||
* 状态名
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package cd.casic.module.machine.enums;
|
||||
|
||||
import cd.casic.framework.commons.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
//ssh远程连接管道类型
|
||||
public enum SSHChanelType implements IntArrayValuable {
|
||||
EXEC(1, "单条命令返回结果"),
|
||||
SHELL(2, "多行命令持续交互"),
|
||||
SUBSYSTEM(3, "文件传输或其它特点服务"),
|
||||
DIRECT_TCP_IP(4, "TCP 端口转发"),
|
||||
X11(5, "X Window 系统转发");
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SSHChanelType::getCode).toArray();
|
||||
|
||||
/**
|
||||
* 状态值
|
||||
*/
|
||||
private final Integer code;
|
||||
|
||||
/**
|
||||
* 状态名
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package cd.casic.module.machine.handler;
|
||||
|
||||
import cd.casic.module.machine.component.WebSocketConnection;
|
||||
import cd.casic.module.machine.component.WebSocketSessionManager;
|
||||
import cd.casic.module.machine.enums.ConnectionStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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;
|
||||
|
||||
import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*;
|
||||
|
||||
//WebSocket处理器
|
||||
@Component("machineWebSocketHandler")
|
||||
public class MachineWebSocketHandler extends TextWebSocketHandler {
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(@NotNull WebSocketSession webSocketSession) {
|
||||
Long machineId = (Long) webSocketSession.getAttributes().get("machineId");
|
||||
//保存webSocketSession信息
|
||||
WebSocketSessionManager.addWebSocketSession(webSocketSession.getId(), webSocketSession);
|
||||
WebSocketSessionManager.addWebSocketConnection(webSocketSession.getId(), WebSocketSessionManager.getWebSocketConnectionByMachineId(machineId));
|
||||
try {
|
||||
webSocketSession.sendMessage(new TextMessage("欢迎连接到 WebSocket 服务器!"));
|
||||
} catch (IOException e) {
|
||||
throw exception(WEBSOCKET_SEND_MESSAGE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理文本消息
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession webSocketSession, TextMessage message) {
|
||||
String payload = message.getPayload();
|
||||
String sessionId = webSocketSession.getId();
|
||||
// 从管理器获取连接
|
||||
WebSocketConnection webSocketConnection = WebSocketSessionManager.getWebSocketConnection(sessionId);
|
||||
|
||||
if (webSocketConnection != null && ConnectionStatus.CONNECTING.equals(webSocketConnection.getConnectionStatus())) {
|
||||
// 转发消息到远程机器
|
||||
webSocketConnection.executeCommand(webSocketSession, payload);
|
||||
} else if (webSocketConnection != null) {
|
||||
webSocketConnection.sendErrorMessage(webSocketSession, "连接已断开,无法发送消息");
|
||||
} else {
|
||||
throw exception(WEBSOCKET_SEND_MESSAGE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession webSocketSession, @NotNull CloseStatus status) {
|
||||
String sessionId = webSocketSession.getId();
|
||||
// 获取并关闭相关的 WebSocketConnection
|
||||
WebSocketConnection webSocketConnection = WebSocketSessionManager.getWebSocketConnection(sessionId);
|
||||
if (webSocketConnection != null) {
|
||||
webSocketConnection.disconnect();
|
||||
}
|
||||
// 从管理器中移除会话和连接
|
||||
WebSocketSessionManager.removeWebSocketSession(sessionId);
|
||||
WebSocketSessionManager.removeWebSocketConnection(sessionId);
|
||||
Long machineInfoId = (Long) webSocketSession.getAttributes().get("machineId");
|
||||
WebSocketSessionManager.removeWebSocketConnectionByMachineId(machineInfoId);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package cd.casic.module.machine.service;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.module.machine.controller.vo.MachineEnvVO;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineEnvDO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* 环境变量服务接口
|
||||
*/
|
||||
|
||||
public interface MachineEnvService {
|
||||
/**
|
||||
* 创建或更新机器的环境变量(一对一关系)
|
||||
*/
|
||||
Long createEnv(@Valid MachineEnvVO machineEnvVO);
|
||||
|
||||
/**
|
||||
* 删除机器的环境变量
|
||||
*/
|
||||
void deleteEnv(Long machineEvnId);
|
||||
|
||||
/**
|
||||
* 获取机器的环境变量
|
||||
*
|
||||
* @param machineId 机器ID
|
||||
* @return 环境变量DTO
|
||||
*/
|
||||
MachineEnvVO getEnv(Long machineId);
|
||||
|
||||
/**
|
||||
* @return 环境变量列表
|
||||
*/
|
||||
PageResult<MachineEnvDO> getEnvPage(@Valid MachineEnvVO machineEnvVO);
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
void deleteEnvList(String ids);
|
||||
|
||||
/*
|
||||
* 修改环境变量
|
||||
*/
|
||||
void updateEnv(@Valid MachineEnvVO machineEnvVO);
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package cd.casic.module.machine.service;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.module.machine.controller.vo.MachineInfoVO;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
|
||||
import cd.casic.module.machine.enums.ConnectionStatus;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface MachineInfoService {
|
||||
/**
|
||||
* 新增机器
|
||||
* @return 新增机器的id
|
||||
*/
|
||||
Long createMachine(MachineInfoVO MachineInfoVO);
|
||||
|
||||
/**
|
||||
* 查看机器列表
|
||||
* @return 分院
|
||||
*/
|
||||
PageResult<MachineInfoDO> listMachineInfo(@Valid MachineInfoVO MachineInfoVO);
|
||||
|
||||
void updateMachineInfo(@Valid MachineInfoVO machineInfoVO);
|
||||
|
||||
Integer updateStatus(@Valid MachineInfoVO machineInfoVO);
|
||||
|
||||
void bindingSecretKey(List<Long> machineInfoIds, Long secretKeyId);
|
||||
|
||||
void deleteMachineInfoList(String machineInfoIds);
|
||||
|
||||
void deleteMachineInfo(Long machineInfoId);
|
||||
|
||||
|
||||
/**
|
||||
* 测试机器连接
|
||||
*
|
||||
* @param id 机器id
|
||||
* @return 连接是否成功
|
||||
*/
|
||||
boolean testConnection(Long id);
|
||||
|
||||
/**
|
||||
* 连接远程机器
|
||||
*
|
||||
* @param id 机器id
|
||||
* @return 连接后端文件树
|
||||
*/
|
||||
Map<String, Object> connect(Long id);
|
||||
|
||||
/**
|
||||
* 获取机器连接状态
|
||||
*
|
||||
* @return 连接状态
|
||||
*/
|
||||
ConnectionStatus getConnectionStatus(Long id);
|
||||
|
||||
/**
|
||||
* 获取所有连接状态
|
||||
*
|
||||
* @return 机器名称到连接状态的映射
|
||||
*/
|
||||
Map<Long, ConnectionStatus> getAllConnectionStatus();
|
||||
|
||||
// /**
|
||||
// * 上传文件到远程机器
|
||||
// *
|
||||
// * @param sessionId 会话ID
|
||||
// * @param localFilePath 本地文件路径
|
||||
// * @param remoteFilePath 远程文件路径
|
||||
// * @return 操作结果
|
||||
// */
|
||||
// boolean uploadFile(String sessionId, String localFilePath, String remoteFilePath);
|
||||
//
|
||||
// /**
|
||||
// * 从远程机器下载文件
|
||||
// *
|
||||
// * @param sessionId 会话ID
|
||||
// * @param remoteFilePath 远程文件路径
|
||||
// * @param localFilePath 本地文件路径
|
||||
// * @return 操作结果
|
||||
// */
|
||||
// boolean downloadFile(String sessionId, String remoteFilePath, String localFilePath);
|
||||
|
||||
/**
|
||||
* 校验机器是否存在
|
||||
*
|
||||
* @param id 机器id
|
||||
*/
|
||||
MachineInfoDO validateMachineInfoExists(Long id);
|
||||
|
||||
/**
|
||||
* 根据路径获得远程文件树
|
||||
*
|
||||
* @param machineId 机器id
|
||||
* @param path 文件夹路径
|
||||
* @return 远程文件树
|
||||
*/
|
||||
Map<String, Object> fileTreeNode(Long machineId, String path);
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package cd.casic.module.machine.service;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.module.machine.controller.vo.MachineProxyVO;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineProxyDO;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 机器代理服务接口
|
||||
*/
|
||||
public interface MachineProxyService {
|
||||
/**
|
||||
* 注册新的机器代理
|
||||
*/
|
||||
Long createProxy(@Valid MachineProxyVO machineProxyVO);
|
||||
|
||||
/**
|
||||
* 更新代理状态
|
||||
*/
|
||||
void updateProxy(@Valid MachineProxyVO machineProxyVO);
|
||||
|
||||
/**
|
||||
* 删除代理
|
||||
*
|
||||
* @param
|
||||
*/
|
||||
void delete(Long id);
|
||||
|
||||
/**
|
||||
* 获取所有代理的状态统计
|
||||
*
|
||||
* @return 状态统计Map
|
||||
*/
|
||||
Map<Integer, Long> getAllProxyStatus();
|
||||
|
||||
|
||||
/**
|
||||
* 批量删除代理
|
||||
*
|
||||
* @param proxyIds 代理ID列表
|
||||
*/
|
||||
void deleteProxyList(String proxyIds);
|
||||
|
||||
PageResult<MachineProxyDO> getProxyPage(@Valid MachineProxyVO machineProxyVO);
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package cd.casic.module.machine.service;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
|
||||
import cd.casic.module.machine.dal.dataobject.SecretKeyDO;
|
||||
import cd.casic.module.machine.controller.vo.SecretKeyVO;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SecretKeyService {
|
||||
Long createSecretKey(@Valid SecretKeyVO secretKeyVO) throws Exception;
|
||||
|
||||
void bindingMachine(@Valid SecretKeyVO secretKeyVO);
|
||||
|
||||
void updateSecretKey(@Valid SecretKeyVO secretKeyVO);
|
||||
|
||||
PageResult<SecretKeyDO> getSecretKeypage(@Valid SecretKeyVO secretKeyVO);
|
||||
|
||||
void deleteSecretKeyList(List<Long> ids);
|
||||
|
||||
SecretKeyVO getSecretKey(Long id);
|
||||
|
||||
void deleteSecretKey(Long id);
|
||||
|
||||
void unbindMachine(@Valid SecretKeyVO secretKeyVO);
|
||||
|
||||
ResponseEntity<InputStreamResource> downloadSecretFile(Long id);
|
||||
|
||||
List<MachineInfoDO> getBindMachine(Long secretKeyId);
|
||||
|
||||
String getKeyContent(Long secretKeyId);
|
||||
}
|
||||
|
@ -0,0 +1,105 @@
|
||||
package cd.casic.module.machine.service.impl;
|
||||
|
||||
import cd.casic.module.machine.controller.vo.MachineEnvVO;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineEnvDO;
|
||||
import cd.casic.module.machine.dal.mysql.MachineEnvMapper;
|
||||
import cd.casic.module.machine.service.MachineEnvService;
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import cd.casic.framework.commons.util.object.BeanUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 环境变量服务实现类
|
||||
*/
|
||||
@Service("machineEnvService")
|
||||
public class MachineEnvServiceImpl implements MachineEnvService {
|
||||
|
||||
@Resource
|
||||
private MachineEnvMapper machineEnvMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createEnv(MachineEnvVO machineEnvVO) {
|
||||
validateMachineEnvAdd(machineEnvVO);
|
||||
// 检查键是否合法
|
||||
validateKey(machineEnvVO.getEnvKey());
|
||||
MachineEnvDO machineEnvDO = BeanUtils.toBean(machineEnvVO, MachineEnvDO.class);
|
||||
machineEnvMapper.insert(machineEnvDO);
|
||||
return machineEnvDO.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteEnv(Long machineEvnId) {
|
||||
machineEnvMapper.deleteById(machineEvnId);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public MachineEnvVO getEnv(Long machineId) {
|
||||
MachineEnvDO machineEnvDO = validateMachineEnvExists(machineId);
|
||||
return BeanUtils.toBean(machineEnvDO, MachineEnvVO.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PageResult<MachineEnvDO> getEnvPage(MachineEnvVO machineEnvVO) {
|
||||
return machineEnvMapper.selectPage(machineEnvVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteEnvList(String ids) {
|
||||
//ids转换为List,使用流
|
||||
List<Long> machineEnvIds = Arrays.stream(ids.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.map(Long::parseLong)
|
||||
.toList();
|
||||
|
||||
machineEnvMapper.deleteBatchIds(machineEnvIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateEnv(MachineEnvVO machineEnvVO) {
|
||||
MachineEnvDO machineEnvDO = validateMachineEnvExists(machineEnvVO.getId());
|
||||
BeanUtils.copyProperties(machineEnvVO, machineEnvDO);
|
||||
machineEnvMapper.updateById(machineEnvDO);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
MachineEnvDO validateMachineEnvExists(Long id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
MachineEnvDO machineEnvDO = machineEnvMapper.selectById(id);
|
||||
if (machineEnvDO == null) {
|
||||
throw exception(MACHINE_ENV_NOT_EXISTS);
|
||||
}
|
||||
return machineEnvDO;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateMachineEnvAdd(MachineEnvVO machineEnvVO) {
|
||||
if (machineEnvVO.getEnvKey() == null || machineEnvVO.getEnvValue() == null) {
|
||||
throw exception(MACHINE_ENV_NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查环境变量键是否合法
|
||||
@VisibleForTesting
|
||||
private void validateKey(String key) {
|
||||
if (!key.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) {
|
||||
throw exception(MACHINE_ENV_KEY_ILLEGAL);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,274 @@
|
||||
package cd.casic.module.machine.service.impl;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.module.machine.component.FileTreeComponent;
|
||||
import cd.casic.module.machine.component.WebSocketSessionManager;
|
||||
import cd.casic.module.machine.controller.vo.SecretKeyVO;
|
||||
import cd.casic.module.machine.enums.AuthenticationType;
|
||||
import cd.casic.module.machine.enums.MachineInfoType;
|
||||
import cd.casic.module.machine.dal.mysql.MachineInfoMapper;
|
||||
import cd.casic.module.machine.controller.vo.MachineInfoVO;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
|
||||
import cd.casic.module.machine.enums.ConnectionStatus;
|
||||
import cd.casic.module.machine.enums.MachineInfoStatus;
|
||||
import cd.casic.module.machine.component.WebSocketConnection;
|
||||
import cd.casic.module.machine.service.MachineInfoService;
|
||||
import cd.casic.module.machine.service.SecretKeyService;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.jcraft.jsch.Session;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import cd.casic.framework.commons.util.object.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 机器信息服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("machineInfoService")
|
||||
public class MachineInfoServiceImpl implements MachineInfoService {
|
||||
|
||||
@Resource
|
||||
private SecretKeyService secretKeyService;
|
||||
|
||||
@Resource
|
||||
private MachineInfoMapper machineInfoMapper;
|
||||
|
||||
@Resource
|
||||
private FileTreeComponent fileTreeComponent;
|
||||
|
||||
@Override
|
||||
public Long createMachine(MachineInfoVO machineInfoVO) {
|
||||
validateMachineEnvAdd(machineInfoVO);
|
||||
validateMachineTagUnique(machineInfoVO.getTag());
|
||||
MachineInfoDO machineInfoDO = BeanUtils.toBean(machineInfoVO, MachineInfoDO.class);
|
||||
if (machineInfoVO.getAuthenticationType() == 2) {
|
||||
Long secretKeyId = machineInfoDO.getSecretKeyId();
|
||||
SecretKeyVO secretKey = secretKeyService.getSecretKey(secretKeyId);
|
||||
if (secretKey == null) {
|
||||
throw exception(SECRET_KEY_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
machineInfoMapper.insert(machineInfoDO);
|
||||
return machineInfoDO.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMachineInfo(MachineInfoVO machineInfoVO) {
|
||||
validateMachineEnvAdd(machineInfoVO);
|
||||
String newTag = machineInfoVO.getTag();
|
||||
MachineInfoDO machineInfoDO = validateMachineInfoExists(machineInfoVO.getId());
|
||||
String oldTag = machineInfoDO.getTag();
|
||||
if (!newTag.equals(oldTag)) {
|
||||
validateMachineTagUnique(newTag);
|
||||
}
|
||||
BeanUtils.copyProperties(machineInfoVO, machineInfoDO);
|
||||
machineInfoMapper.updateById(machineInfoDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer updateStatus(MachineInfoVO machineInfoVO) {
|
||||
machineInfoMapper.updateStatus(machineInfoVO.getId(), machineInfoVO.getStatus());
|
||||
return machineInfoVO.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<MachineInfoDO> listMachineInfo(MachineInfoVO machineInfoVO) {
|
||||
return machineInfoMapper.selectPage(machineInfoVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindingSecretKey(List<Long> machineInfoIds, Long secretKeyId) {
|
||||
machineInfoMapper.bindingSecretKey(machineInfoIds, secretKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional//其中一个在线,那么就回滚
|
||||
public void deleteMachineInfoList(String machineInfoIds) {
|
||||
List<Long> machineInfoIdList = Arrays.stream(machineInfoIds.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.map(Long::parseLong)
|
||||
.toList();
|
||||
machineInfoIdList.forEach(this::deleteMachineInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMachineInfo(Long machineInfoId) {
|
||||
MachineInfoDO machineInfoDO = validateMachineInfoExists(machineInfoId);
|
||||
validateMachineEnable(machineInfoDO);
|
||||
machineInfoMapper.deleteById(machineInfoId);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean testConnection(Long id) {
|
||||
//先查询机器是否存在,在判断机器可用性
|
||||
MachineInfoDO machineInfoDO = validateMachineInfoExists(id);
|
||||
validateMachineUnEnable(machineInfoDO);
|
||||
log.info("测试机器连接: {}", machineInfoDO.getHostIp());
|
||||
WebSocketConnection webSocketConnection = createWebSocketConnection(machineInfoDO);
|
||||
webSocketConnection.initConnection(machineInfoDO);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> connect(Long id) {
|
||||
//todo使用代理机器的情况还未完成
|
||||
WebSocketConnection webSocketConnection = new WebSocketConnection(this.secretKeyService);
|
||||
MachineInfoDO machineInfoDO = validateMachineInfoExists(id);
|
||||
//初始化连接
|
||||
webSocketConnection.initConnection(machineInfoDO);
|
||||
WebSocketSessionManager.addWebSocketConnectionByMachineId(id, webSocketConnection);
|
||||
return fileTreeComponent.getRemoteFileTree(webSocketConnection.getSshSession(), "/");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionStatus getConnectionStatus(Long id) {
|
||||
validateMachineInfoExists(id);
|
||||
WebSocketConnection webSocketConnection = WebSocketSessionManager.getWebSocketConnectionByMachineId(id);
|
||||
return webSocketConnection == null ? ConnectionStatus.DISCONNECTED : webSocketConnection.getConnectionStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, ConnectionStatus> getAllConnectionStatus() {
|
||||
return WebSocketSessionManager.getAllWebSocketConnections().entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
entry -> entry.getValue().getConnectionStatus()
|
||||
));
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public boolean uploadFile(String sessionId, String localFilePath, String remoteFilePath) {
|
||||
// log.info("上传文件: {} -> {}, 会话ID: {}", localFilePath, remoteFilePath, sessionId);
|
||||
// WebSocketConnection webSocketConnection = sessionConnectionMap.get(sessionId);
|
||||
// if (webSocketConnection == null||webSocketConnection.getConnectionStatus() != ConnectionStatus.CONNECTED) {
|
||||
// throw exception(SESSION_NOT_CONNECT);
|
||||
// }
|
||||
// try {
|
||||
// return webSocketConnection.uploadFile(localFilePath, remoteFilePath);
|
||||
// } catch (Exception e) {
|
||||
// log.error("文件上传失败: {}", e.getMessage(), e);
|
||||
// throw exception(FILE_UPLOAD_ERROR);
|
||||
// }
|
||||
// }
|
||||
|
||||
// @Override
|
||||
// public boolean downloadFile(String sessionId, String remoteFilePath, String localFilePath) {
|
||||
// log.info("下载文件: {} -> {}, 会话ID: {}", remoteFilePath, localFilePath, sessionId);
|
||||
//
|
||||
// WebSocketConnection webSocketConnection = sessionConnectionMap.get(sessionId);
|
||||
// if (webSocketConnection == null||webSocketConnection.getConnectionStatus() != ConnectionStatus.CONNECTED) {
|
||||
// throw new RuntimeException("会话不存在: " + sessionId);
|
||||
// }
|
||||
// try {
|
||||
// return webSocketConnection.downloadFile(remoteFilePath, localFilePath);
|
||||
// } catch (Exception e) {
|
||||
// log.error("文件下载失败: {}", e.getMessage(), e);
|
||||
// throw exception(FILE_DOWNLOAD_ERROR);
|
||||
// }
|
||||
// }
|
||||
|
||||
@VisibleForTesting
|
||||
void validateMachineEnvAdd(MachineInfoVO machineInfoVO) {
|
||||
if (machineInfoVO.getHostIp().isEmpty()) {
|
||||
throw exception(MACHINE_INFO_HOST_IP_NULL);
|
||||
}
|
||||
if (machineInfoVO.getUsername().isEmpty()) {
|
||||
throw exception(MACHINE_INFO_USER_NAME_NULL);
|
||||
}
|
||||
if (machineInfoVO.getTag().isEmpty()) {
|
||||
throw exception(MACHINE_INFO_TAG_NULL);
|
||||
}
|
||||
|
||||
if (machineInfoVO.getAuthenticationType() != null) {
|
||||
boolean flag = true;
|
||||
for (int type : AuthenticationType.ARRAYS) {
|
||||
if (type == machineInfoVO.getAuthenticationType()) {
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
throw exception(MACHINE_INFO_AUTHENTICATION_TYPE_NOT_EXISTS);
|
||||
}
|
||||
} else {
|
||||
throw exception(MACHINE_INFO_AUTHENTICATION_TYPE_NULL);
|
||||
}
|
||||
|
||||
|
||||
if (machineInfoVO.getMachineInfoType() != null) {
|
||||
boolean flag = true;
|
||||
for (int type : MachineInfoType.ARRAYS) {
|
||||
if (type == machineInfoVO.getMachineInfoType()) {
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
throw exception(MACHINE_INFO_TYPE_NOT_EXISTS);
|
||||
}
|
||||
} else {
|
||||
throw exception(MACHINE_INFO_TYPE_NULL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateMachineTagUnique(String tag) {
|
||||
if (machineInfoMapper.existsByTag(tag)) {
|
||||
throw exception(MACHINE_INFO_TAG_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MachineInfoDO validateMachineInfoExists(Long id) {
|
||||
if (id == null) {
|
||||
throw exception(MACHINE_INFO_NULL);
|
||||
}
|
||||
MachineInfoDO machineInfoDO = machineInfoMapper.selectById(id);
|
||||
if (machineInfoDO == null) {
|
||||
throw exception(MACHINE_INFO_NULL);
|
||||
}
|
||||
return machineInfoDO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> fileTreeNode(Long machineId, String path) {
|
||||
validateMachineInfoExists(machineId);
|
||||
Session sshSession = WebSocketSessionManager.getWebSocketConnectionByMachineId(machineId).getSshSession();
|
||||
return fileTreeComponent.getRemoteFileTree(sshSession, path);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateMachineEnable(MachineInfoDO machineInfoDO) {
|
||||
if (machineInfoDO.getStatus() == MachineInfoStatus.ENABLE.getCode()) {
|
||||
throw exception(MACHINE_ENABLE);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateMachineUnEnable(MachineInfoDO machineInfoDO) {
|
||||
|
||||
if (machineInfoDO.getStatus() == MachineInfoStatus.UN_ENABLE.getCode()) {
|
||||
throw exception(MACHINE_UN_ENABLE);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
WebSocketConnection createWebSocketConnection(MachineInfoDO machineInfoDO) {
|
||||
if (WebSocketSessionManager.containsMachineId(machineInfoDO.getId())) {
|
||||
return WebSocketSessionManager.getWebSocketConnectionByMachineId((machineInfoDO.getId()));
|
||||
} else {
|
||||
return new WebSocketConnection(this.secretKeyService);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
package cd.casic.module.machine.service.impl;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.module.machine.controller.vo.MachineProxyVO;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineProxyDO;
|
||||
import cd.casic.module.machine.enums.MachineProxyStatus;
|
||||
import cd.casic.module.machine.enums.MachineProxyType;
|
||||
import cd.casic.module.machine.dal.mysql.MachineProxyMapper;
|
||||
import cd.casic.module.machine.service.MachineProxyService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import jakarta.annotation.Resource;
|
||||
import cd.casic.framework.commons.util.object.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*;
|
||||
import static com.baomidou.mybatisplus.extension.toolkit.Db.save;
|
||||
|
||||
/**
|
||||
* 机器代理服务实现类
|
||||
*/
|
||||
@Service("machineProxyService")
|
||||
public class MachineProxyServiceImpl implements MachineProxyService {
|
||||
@Resource
|
||||
private MachineProxyMapper machineProxyMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createProxy(MachineProxyVO machineProxyVO) {
|
||||
validateMachineProxyAdd(machineProxyVO);
|
||||
// 创建代理记录
|
||||
MachineProxyDO machineProxyDO = BeanUtils.toBean(machineProxyVO, MachineProxyDO.class);
|
||||
save(machineProxyDO);
|
||||
return machineProxyDO.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProxy(MachineProxyVO machineProxyVO) {
|
||||
// 参数校验
|
||||
MachineProxyDO machineProxyDO = validateMachineProxyExists(machineProxyVO.getId());
|
||||
// 更新状态
|
||||
BeanUtils.copyProperties(machineProxyVO, machineProxyDO);
|
||||
machineProxyMapper.updateById(machineProxyDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Long id) {
|
||||
MachineProxyDO machineProxyDO = validateMachineProxyExists(id);
|
||||
validateMachineProxyOnline(machineProxyDO);
|
||||
machineProxyMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateMachineProxyOnline(MachineProxyDO machineProxyDO) {
|
||||
if (machineProxyDO.getStatus() == MachineProxyStatus.ONLINE.getCode()) {
|
||||
throw exception(MACHINE_PROXY_IS_ONLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, Long> getAllProxyStatus() {
|
||||
List<MachineProxyDO> proxyList = machineProxyMapper.selectList(new QueryWrapper<>());
|
||||
if (CollectionUtils.isEmpty(proxyList)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return proxyList.stream()
|
||||
.map(MachineProxyDO::getStatus)
|
||||
.collect(Collectors.groupingBy(
|
||||
Function.identity(),
|
||||
// 统计每个分组的元素数量
|
||||
Collectors.counting()
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteProxyList(String ids) {
|
||||
if (ids == null) {
|
||||
return;
|
||||
}
|
||||
List<Long> machineProxyIds = Arrays.stream(ids.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.map(Long::parseLong)
|
||||
.toList();
|
||||
// 批量逻辑删除
|
||||
machineProxyIds.forEach(this::delete);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<MachineProxyDO> getProxyPage(MachineProxyVO machineProxyVO) {
|
||||
return machineProxyMapper.selectPage(machineProxyVO);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
MachineProxyDO validateMachineProxyExists(Long id) {
|
||||
if (id == null) {
|
||||
throw exception(MACHINE_PROXY_NOT_EXISTS);
|
||||
}
|
||||
MachineProxyDO machineProxyDO = machineProxyMapper.selectById(id);
|
||||
if (machineProxyDO == null) {
|
||||
throw exception(MACHINE_PROXY_NOT_EXISTS);
|
||||
}
|
||||
return machineProxyDO;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateMachineProxyAdd(MachineProxyVO machineProxyVO) {
|
||||
if (machineProxyVO.getHostIp() == null) {
|
||||
throw exception(MACHINE_PROXY_HOST_IP_NULL);
|
||||
}
|
||||
if (machineProxyVO.getUsername() == null) {
|
||||
throw exception(MACHINE_PROXY_USER_NAME_NULL);
|
||||
}
|
||||
|
||||
// 校验代理类型
|
||||
int[] arrays = MachineProxyType.ARRAYS;
|
||||
if (Arrays.stream(arrays).filter(i -> i == machineProxyVO.getProxyType()).findAny().isEmpty()) {
|
||||
throw exception(MACHINE_PROXY_TYPE_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,226 @@
|
||||
package cd.casic.module.machine.service.impl;
|
||||
|
||||
import cd.casic.framework.commons.pojo.PageResult;
|
||||
import cd.casic.framework.commons.util.object.BeanUtils;
|
||||
import cd.casic.module.machine.controller.vo.SecretKeyVO;
|
||||
import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
|
||||
import cd.casic.module.machine.dal.dataobject.SecretKeyDO;
|
||||
import cd.casic.module.machine.dal.mysql.MachineInfoMapper;
|
||||
import cd.casic.module.machine.dal.mysql.SecretKeyMapper;
|
||||
import cd.casic.module.machine.service.MachineInfoService;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cd.casic.module.machine.utils.AliYunOssClient;
|
||||
import cd.casic.module.machine.service.SecretKeyService;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import static cd.casic.framework.commons.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cd.casic.module.machine.contants.MachineErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 密钥服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("secretKeyService")
|
||||
public class SecretKeyServiceImpl implements SecretKeyService {
|
||||
|
||||
@Resource
|
||||
private MachineInfoService machineInfoService;
|
||||
|
||||
@Resource
|
||||
private AliYunOssClient aliYunOssClient;
|
||||
|
||||
@Resource
|
||||
private SecretKeyMapper secretKeyMapper;
|
||||
|
||||
@Resource
|
||||
private MachineInfoMapper machineInfoMapper;
|
||||
|
||||
@Override
|
||||
public SecretKeyVO getSecretKey(Long id) {
|
||||
SecretKeyDO secretKeyDO = validateSecretKeyExists(id);
|
||||
return BeanUtils.toBean(secretKeyDO, SecretKeyVO.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSecretKey(Long id) {
|
||||
validateSecretKeyExists(id);
|
||||
secretKeyMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindMachine(SecretKeyVO secretKeyVO) {
|
||||
machineInfoService.bindingSecretKey(secretKeyVO.getMachineInfoIds(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<InputStreamResource> downloadSecretFile(Long id) {
|
||||
// 获取私钥内容
|
||||
String keyContent = getKeyContent(id);
|
||||
if (keyContent == null || keyContent.isEmpty()) {
|
||||
throw exception(SECRET_KEY_NULL);
|
||||
}
|
||||
// 将字符串转换为输入流
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(keyContent.getBytes(StandardCharsets.UTF_8));
|
||||
// 设置响应头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=private_key.pem");
|
||||
headers.add(HttpHeaders.CONTENT_TYPE, "application/octet-stream");
|
||||
headers.add(HttpHeaders.CONTENT_LENGTH, String.valueOf(keyContent.getBytes(StandardCharsets.UTF_8).length));
|
||||
// 返回带有文件流的响应实体
|
||||
return ResponseEntity.ok()
|
||||
.headers(headers)
|
||||
.body(new InputStreamResource(inputStream));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MachineInfoDO> getBindMachine(Long secretKeyId) {
|
||||
return machineInfoMapper.selectBindMachineBySecretKey(secretKeyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createSecretKey(SecretKeyVO secretKeyVO) {
|
||||
validateSecretKeyAdd(secretKeyVO);
|
||||
String localPath = secretKeyVO.getPath();
|
||||
//将反斜杠替换为双反斜杠(Windows路径转义)
|
||||
localPath = localPath.replace("\\", "\\\\");
|
||||
String ossPath = upLoadSecretKey(localPath);
|
||||
//检查得到的oss路径是否为空
|
||||
validateSecretKeyPath(ossPath);
|
||||
secretKeyVO.setPath(ossPath);
|
||||
SecretKeyDO secretKeyDO = BeanUtils.toBean(secretKeyVO, SecretKeyDO.class);
|
||||
secretKeyMapper.insert(secretKeyDO);
|
||||
return secretKeyDO.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSecretKey(SecretKeyVO secretKeyVO) {
|
||||
SecretKeyDO secretKeyDO = validateSecretKeyExists(secretKeyVO.getId());
|
||||
//路径改变==改变密钥
|
||||
if (!secretKeyDO.getPath().equals(secretKeyVO.getPath())) {
|
||||
String ossPath = upLoadSecretKey(secretKeyVO.getPath());
|
||||
BeanUtils.copyProperties(secretKeyVO, secretKeyDO);
|
||||
secretKeyDO.setPath(ossPath);
|
||||
} else {
|
||||
BeanUtils.copyProperties(secretKeyVO, secretKeyDO);
|
||||
}
|
||||
secretKeyMapper.updateById(secretKeyDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindingMachine(SecretKeyVO secretKeyVO) {
|
||||
validateSecretKeyExists(secretKeyVO.getId());
|
||||
machineInfoService.bindingSecretKey(secretKeyVO.getMachineInfoIds(), secretKeyVO.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteSecretKeyList(List<Long> ids) {
|
||||
ids.forEach(
|
||||
secretKeyId -> {
|
||||
SecretKeyDO secretKeyDO = validateSecretKeyExists(secretKeyId);
|
||||
if (secretKeyDO.getPath() != null && !secretKeyDO.getPath().isEmpty()) {
|
||||
try {
|
||||
//文件名
|
||||
//删除子目录文件,需要在前面加上根目录文件路径
|
||||
String fileName = secretKeyDO.getPath().substring(secretKeyDO.getPath().lastIndexOf("/") + 1);
|
||||
aliYunOssClient.delete(fileName);
|
||||
} catch (Exception e) {
|
||||
throw exception(DELETE_FILE_FAIL);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
//绑定的机器全部设置为空
|
||||
machineInfoService.bindingSecretKey(ids, null);
|
||||
secretKeyMapper.deleteBatchIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<SecretKeyDO> getSecretKeypage(SecretKeyVO secretKeyVO) {
|
||||
return secretKeyMapper.selectPage(secretKeyVO);
|
||||
}
|
||||
|
||||
public String upLoadSecretKey(String localPath) {
|
||||
//使用S3FileClient上传文件
|
||||
aliYunOssClient.init();
|
||||
//传输到指定文件,需要在path前面加上文件路径
|
||||
String path = IdUtil.fastSimpleUUID() + ".txt";
|
||||
//上传文件是从本地上传,这里传的是本地文件地址
|
||||
byte[] content = ResourceUtil.readBytes(localPath);
|
||||
String ossPath;
|
||||
try {
|
||||
ossPath = aliYunOssClient.upload(content, path, "txt");
|
||||
} catch (Exception e) {
|
||||
throw exception(UPLOADING_FILE_FAIL);
|
||||
}
|
||||
return ossPath;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateSecretKeyAdd(SecretKeyVO secretKeyVO) {
|
||||
if (secretKeyVO == null) {
|
||||
throw exception(SECRET_KEY_NULL);
|
||||
}
|
||||
if (secretKeyVO.getPath().isEmpty()) {
|
||||
throw exception(SECRET_KEY_PATH_NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateSecretKeyPath(String path) {
|
||||
if (path.isEmpty()) {
|
||||
throw exception(SECRET_KEY_PATH_NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@VisibleForTesting
|
||||
SecretKeyDO validateSecretKeyExists(Long id) {
|
||||
if (id == null) {
|
||||
throw exception(SECRET_KEY_NOT_EXISTS);
|
||||
}
|
||||
SecretKeyDO secretKeyDO = secretKeyMapper.selectById(id);
|
||||
if (secretKeyDO == null) {
|
||||
throw exception(SECRET_KEY_NOT_EXISTS);
|
||||
}
|
||||
return secretKeyDO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKeyContent(Long secretKeyId) {
|
||||
if (secretKeyId == null) {
|
||||
return null;
|
||||
}
|
||||
SecretKeyVO secretKey = getSecretKey(secretKeyId);
|
||||
byte[] content;
|
||||
try {
|
||||
content = aliYunOssClient.getContent(secretKey.getPath().substring(secretKey.getPath().lastIndexOf("/") + 1));
|
||||
} catch (Exception e) {
|
||||
log.error("读取密钥文件失败", e);
|
||||
throw exception(READ_SECRET_CONTENT_ERROR);
|
||||
}
|
||||
//改为S3FileClient读取
|
||||
InputStream read = new ByteArrayInputStream(content);
|
||||
try {
|
||||
return StreamUtils.copyToString(read, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package cd.casic.module.machine.utils;
|
||||
|
||||
import cd.casic.module.infra.framework.file.core.client.s3.S3FileClient;
|
||||
import cd.casic.module.infra.framework.file.core.client.s3.S3FileClientConfig;
|
||||
|
||||
|
||||
public class AliYunOssClient extends S3FileClient {
|
||||
public AliYunOssClient(Long id, S3FileClientConfig config) {
|
||||
super(id, config);
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for machine_env
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `machine_env`;
|
||||
CREATE TABLE `machine_env` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`create_date` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
|
||||
`update_date` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0),
|
||||
`machine_id` bigint NULL DEFAULT NULL,
|
||||
`env_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
||||
`env_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
|
||||
`sensitive` tinyint(1) NULL DEFAULT NULL COMMENT '是否敏感(0=false, 1=true, NULL=未设置)', -- 改为 tinyint 存储布尔值
|
||||
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_machine_env_key` (`machine_id`, `env_key`) -- 同一机器下 env_key 唯一
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for machine_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `machine_info`;
|
||||
CREATE TABLE `machine_info` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`create_date` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
|
||||
`update_date` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0),
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`host_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`status_code` int NULL DEFAULT NULL COMMENT '状态编码(关联字典表)', -- 改为 int 类型
|
||||
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`ssh_port` int NULL DEFAULT NULL COMMENT 'SSH端口号(整数)', -- 改为 int 类型
|
||||
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`secret_key_id` bigint NULL DEFAULT NULL COMMENT '密钥ID(逻辑关联,无外键)',
|
||||
`machine_proxy_id` bigint NULL DEFAULT NULL COMMENT '代理ID(逻辑关联,无外键)',
|
||||
`authentication_type_code` int NULL DEFAULT NULL COMMENT '认证类型编码',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_secret_key_id` (`secret_key_id`), -- 保留索引优化查询
|
||||
KEY `idx_machine_proxy_id` (`machine_proxy_id`)
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for machine_proxy
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `machine_proxy`;
|
||||
CREATE TABLE `machine_proxy` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`create_date` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
|
||||
`update_date` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0),
|
||||
`host_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`ssh_port` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`proxy_type_code` tinyint(1) NULL DEFAULT NULL COMMENT '代理类型编码(关联字典表)',
|
||||
`version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`status_code` tinyint(1) NULL DEFAULT NULL COMMENT '状态编码(关联字典表)', -- 改为 tinyint(1) 类型
|
||||
`last_heartbeat_time` datetime(0) NULL DEFAULT NULL,
|
||||
`config` json NULL,
|
||||
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for machine_secret_key
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `machine_secret_key`;
|
||||
CREATE TABLE `machine_secret_key` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`create_date` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
|
||||
`update_date` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0),
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
@ -0,0 +1,184 @@
|
||||
//package com.casic.machine.service.impl;
|
||||
//
|
||||
//import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
//import com.casic.commons.exception.ServiceException;
|
||||
//import com.casic.commons.utils.PageResult;
|
||||
//import com.casic.machine.dto.MachineEnvDTO;
|
||||
//import com.casic.machine.entity.MachineEnv;
|
||||
//import com.casic.machine.mapper.MachineEnvMapper;
|
||||
//import com.casic.machine.service.MachineEnvService;
|
||||
//import org.junit.jupiter.api.BeforeEach;
|
||||
//import org.junit.jupiter.api.Test;
|
||||
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
//import org.springframework.boot.test.context.SpringBootTest;
|
||||
//import org.springframework.test.annotation.Rollback;
|
||||
//import org.springframework.test.context.jdbc.Sql;
|
||||
//import org.springframework.transaction.annotation.Transactional;
|
||||
//
|
||||
//import java.util.Date;
|
||||
//import java.util.List;
|
||||
//
|
||||
//import static org.junit.jupiter.api.Assertions.*;
|
||||
//
|
||||
//@SpringBootTest
|
||||
//@Transactional
|
||||
//@Rollback(value = true) // 测试后回滚数据
|
||||
//@Sql(scripts = {"classpath:sql/machine_env_test_data.sql"}) // 初始化测试数据(可选)
|
||||
//public class MachineEnvServiceImplTest {
|
||||
//
|
||||
// @Autowired
|
||||
// private MachineEnvService machineEnvService;
|
||||
//
|
||||
// @Autowired
|
||||
// private MachineEnvMapper machineEnvMapper;
|
||||
//
|
||||
// private MachineEnvDTO validDto;
|
||||
// private MachineEnvDTO invalidKeyDto;
|
||||
// private Long existingMachineId;
|
||||
// private Long nonExistingMachineId;
|
||||
//
|
||||
// @BeforeEach
|
||||
// public void setUp() {
|
||||
// // 准备测试数据
|
||||
// existingMachineId = 1L; // 假设数据库中存在ID为1的机器环境变量
|
||||
// nonExistingMachineId = 999L; // 不存在的机器ID
|
||||
//
|
||||
// // 有效测试数据
|
||||
// validDto = new MachineEnvDTO();
|
||||
// validDto.setMachineInfoId(existingMachineId);
|
||||
// validDto.setEnvKey("TEST_ENV_KEY");
|
||||
// validDto.setEnvValue("test-value");
|
||||
// validDto.setSensitive(true);
|
||||
//
|
||||
// // 无效Key测试数据(包含非法字符)
|
||||
// invalidKeyDto = new MachineEnvDTO();
|
||||
// invalidKeyDto.setMachineInfoId(existingMachineId);
|
||||
// invalidKeyDto.setEnvKey("test-env-key"); // 包含'-',不符合正则
|
||||
// invalidKeyDto.setEnvValue("test-value");
|
||||
// }
|
||||
//
|
||||
// // ==================== 更新环境变量测试 ====================
|
||||
// @Test
|
||||
// void testUpdateEnv_ValidData_ShouldSucceed() {
|
||||
// // 执行更新(假设数据库中已存在machineId=1的记录)
|
||||
// boolean result = machineEnvService.update(validDto);
|
||||
//
|
||||
// // 验证结果
|
||||
// assertTrue(result);
|
||||
//
|
||||
// // 检查数据库数据是否更新
|
||||
// MachineEnv updatedEnv = machineEnvMapper.selectOne(
|
||||
// new LambdaQueryWrapper<MachineEnv>().eq(MachineEnv::getMachineId, existingMachineId)
|
||||
// );
|
||||
// assertNotNull(updatedEnv);
|
||||
// assertEquals(validDto.getEnvKey(), updatedEnv.getEnvKey());
|
||||
// assertEquals(validDto.getEnvValue(), updatedEnv.getEnvValue());
|
||||
// assertEquals(validDto.getSensitive(), updatedEnv.getSensitive());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testUpdateEnv_NullDto_ShouldThrowException() {
|
||||
//
|
||||
// MachineEnvDTO machineEnvDTO = new MachineEnvDTO();
|
||||
// assertThrows(ServiceException.class, () -> {
|
||||
// machineEnvService.update(machineEnvDTO);
|
||||
// }, "环境变量不能为空");
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testUpdateEnv_InvalidKey_ShouldThrowException() {
|
||||
// assertThrows(ServiceException.class, () -> {
|
||||
// machineEnvService.update(invalidKeyDto);
|
||||
// }, "环境变量键不合法");
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testUpdateEnv_SensitiveKey_ShouldMarkAsSensitive() {
|
||||
// MachineEnvDTO sensitiveDto = new MachineEnvDTO();
|
||||
// sensitiveDto.setMachineInfoId(existingMachineId);
|
||||
// sensitiveDto.setEnvKey("DB_PASSWORD"); // 包含敏感词
|
||||
// sensitiveDto.setEnvValue("secret");
|
||||
//
|
||||
// machineEnvService.update(sensitiveDto);
|
||||
//
|
||||
// MachineEnv env = machineEnvMapper.selectOne(
|
||||
// new LambdaQueryWrapper<MachineEnv>().eq(MachineEnv::getMachineId, existingMachineId)
|
||||
// );
|
||||
// assertTrue(env.getSensitive());
|
||||
// }
|
||||
//
|
||||
// // ==================== 删除环境变量测试 ====================
|
||||
// @Test
|
||||
// void testDeleteByMachineId_ExistingId_ShouldSucceed() {
|
||||
// machineEnvService.deleteByMachineId(existingMachineId);
|
||||
// MachineEnv env = machineEnvMapper.selectById(existingMachineId);
|
||||
// assertNull(env);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testDeleteByMachineId_NonExistingId_ShouldDoNothing() {
|
||||
// machineEnvService.deleteByMachineId(nonExistingMachineId);
|
||||
// // 不抛出异常,静默处理
|
||||
// }
|
||||
//
|
||||
// // ==================== 根据机器ID查询测试 ====================
|
||||
// @Test
|
||||
// void testGetByMachineId_ExistingId_ShouldReturnDto() {
|
||||
// MachineEnvDTO dto = machineEnvService.getByMachineId(existingMachineId);
|
||||
// assertNotNull(dto);
|
||||
// assertEquals(existingMachineId, dto.getMachineInfoId());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testGetByMachineId_NonExistingId_ShouldReturnNull() {
|
||||
// MachineEnvDTO dto = machineEnvService.getByMachineId(nonExistingMachineId);
|
||||
// assertNull(dto);
|
||||
// }
|
||||
//
|
||||
// // ==================== 列表查询测试 ====================
|
||||
// @Test
|
||||
// void testListEnv_Pagination_ShouldReturnValidPage() {
|
||||
// MachineEnvDTO queryDto = new MachineEnvDTO();
|
||||
// queryDto.setPageIndex(1);
|
||||
// queryDto.setPageSize(10);
|
||||
// queryDto.setEnvKey("TEST"); // 假设测试数据中存在包含"TEST"的键
|
||||
//
|
||||
// PageResult<MachineEnvDTO> pageResult = machineEnvService.listEnv(queryDto);
|
||||
//
|
||||
// assertNotNull(pageResult.getList());
|
||||
// assertTrue(pageResult.getTotal() >= 0);
|
||||
// assertEquals(1, pageResult.getPageNum());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testListEnv_SortByCreateTime_ShouldBeOrdered() {
|
||||
// MachineEnvDTO queryDto = new MachineEnvDTO();
|
||||
// queryDto.setSortField("createTime");
|
||||
// queryDto.setSortDirection("desc");
|
||||
//
|
||||
// PageResult<MachineEnvDTO> pageResult = machineEnvService.listEnv(queryDto);
|
||||
//
|
||||
// List<MachineEnvDTO> list = pageResult.getList();
|
||||
// if (!list.isEmpty()) {
|
||||
// Date prevDate = list.get(0).getCreateDate();
|
||||
// for (int i = 1; i < list.size(); i++) {
|
||||
// Date currDate = list.get(i).getCreateDate();
|
||||
// assertTrue(currDate.before(prevDate) || currDate.equals(prevDate), "排序应为降序");
|
||||
// prevDate = currDate;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // ==================== 批量删除测试 ====================
|
||||
// @Test
|
||||
// void testDeleteList_ValidIds_ShouldDeleteBatch() {
|
||||
// // 假设测试数据中有ID为1和2的记录
|
||||
// List<Long> ids = List.of(1L, 2L);
|
||||
// machineEnvService.deleteList(ids);
|
||||
//
|
||||
// long count = machineEnvMapper.selectCount(new LambdaQueryWrapper<MachineEnv>().in(MachineEnv::getId, ids));
|
||||
// assertEquals(0, count);
|
||||
// }
|
||||
//
|
||||
//
|
||||
//}
|
@ -0,0 +1,222 @@
|
||||
//package com.casic.machine.service.impl;
|
||||
//
|
||||
//import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
//import com.casic.commons.exception.ServiceException;
|
||||
//import com.casic.commons.utils.EnumUtils;
|
||||
//import com.casic.commons.utils.PageResult;
|
||||
//import com.casic.machine.dto.MachineProxyDTO;
|
||||
//import com.casic.machine.entity.MachineProxy;
|
||||
//import com.casic.machine.enums.MachineProxyStatus;
|
||||
//import com.casic.machine.enums.MachineProxyType;
|
||||
//import com.casic.machine.mapper.MachineProxyMapper;
|
||||
//import com.casic.machine.service.MachineProxyService;
|
||||
//import org.junit.jupiter.api.BeforeEach;
|
||||
//import org.junit.jupiter.api.Test;
|
||||
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
//import org.springframework.boot.test.context.SpringBootTest;
|
||||
//import org.springframework.test.annotation.Rollback;
|
||||
//import org.springframework.test.context.jdbc.Sql;
|
||||
//import org.springframework.transaction.annotation.Transactional;
|
||||
//
|
||||
//import java.util.*;
|
||||
//
|
||||
//import static org.junit.jupiter.api.Assertions.*;
|
||||
//
|
||||
//@SpringBootTest
|
||||
//@Transactional
|
||||
//@Rollback(true)
|
||||
//@Sql(scripts = {"classpath:sql/machine_proxy_test_data.sql"})
|
||||
//public class MachineProxyServiceImplTest {
|
||||
//
|
||||
// @Autowired
|
||||
// private MachineProxyService machineProxyService;
|
||||
//
|
||||
// @Autowired
|
||||
// private MachineProxyMapper machineProxyMapper;
|
||||
//
|
||||
// private MachineProxyDTO validProxyDTO;
|
||||
// private Long existingProxyId;
|
||||
// private Long nonExistingProxyId;
|
||||
//
|
||||
// @BeforeEach
|
||||
// public void setUp() {
|
||||
// // 初始化测试数据(假设 SQL 脚本已插入一条状态为 OFFLINE 的代理)
|
||||
// existingProxyId = 1L;
|
||||
// nonExistingProxyId = 999L;
|
||||
//
|
||||
// // 有效代理 DTO
|
||||
// validProxyDTO = new MachineProxyDTO();
|
||||
// validProxyDTO.setProxyType(MachineProxyType.SOCKS5.getMessage());
|
||||
// validProxyDTO.setHostIp("192.168.1.100");
|
||||
// validProxyDTO.setSshPort("22");
|
||||
// validProxyDTO.setUsername("test_user");
|
||||
// validProxyDTO.setStatus(MachineProxyStatus.ONLINE.getMessage());
|
||||
// }
|
||||
//
|
||||
// // ============================== 注册代理测试 ==============================
|
||||
// @Test
|
||||
// void testRegister_ValidData_ShouldSucceed() {
|
||||
// // 执行注册
|
||||
// boolean result = machineProxyService.register(validProxyDTO);
|
||||
// assertTrue(result, "注册失败");
|
||||
//
|
||||
// // 使用 Lambda 表达式查询(推荐)
|
||||
// MachineProxy proxy = machineProxyMapper.selectOne(
|
||||
// new LambdaQueryWrapper<MachineProxy>()
|
||||
// .eq(MachineProxy::getHostIp, validProxyDTO.getHostIp())
|
||||
// );
|
||||
//
|
||||
// // 断言数据存在
|
||||
// assertNotNull(proxy, "代理记录未写入数据库");
|
||||
// assertEquals(MachineProxyType.SOCKS5.getCode(), proxy.getProxyTypeCode());
|
||||
// assertEquals(MachineProxyStatus.INSTALLING.getCode(), proxy.getStatusCode());
|
||||
// assertEquals(validProxyDTO.getHostIp(), proxy.getHostIp(), "IP 地址不一致");
|
||||
// }
|
||||
//
|
||||
// // ============================== 更新状态测试 ==============================
|
||||
// @Test
|
||||
// void testUpdateStatus_ExistingProxy_ShouldUpdateStatus() {
|
||||
// // 准备数据:查询现有代理(状态为 OFFLINE)
|
||||
// MachineProxy proxy = machineProxyMapper.selectById(existingProxyId);
|
||||
// assertEquals(MachineProxyStatus.OFFLINE.getCode(), proxy.getStatusCode());
|
||||
//
|
||||
// // 执行状态更新为 ONLINE
|
||||
// validProxyDTO.setId(existingProxyId);
|
||||
// validProxyDTO.setStatus(MachineProxyStatus.ONLINE.getMessage());
|
||||
// boolean result = machineProxyService.updateStatus(validProxyDTO);
|
||||
//
|
||||
// // 验证结果
|
||||
// assertTrue(result);
|
||||
// proxy = machineProxyMapper.selectById(existingProxyId);
|
||||
// assertEquals(MachineProxyStatus.ONLINE.getCode(), proxy.getStatusCode());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testUpdateStatus_NullDto_ShouldThrowException() {
|
||||
// assertThrows(ServiceException.class, () -> {
|
||||
// machineProxyService.updateStatus(null);
|
||||
// }, "MachineProxyDTO对象为空");
|
||||
// }
|
||||
//
|
||||
// // ============================== 心跳测试 ==============================
|
||||
// @Test
|
||||
// void testHeartbeat_ValidData_ShouldUpdateVersionAndStatus() {
|
||||
// // 准备数据:现有代理状态为 OFFLINE,版本为 1.0.0
|
||||
// MachineProxy proxy = machineProxyMapper.selectById(existingProxyId);
|
||||
// assertEquals("1.0.0", proxy.getVersion());
|
||||
// assertEquals(MachineProxyStatus.OFFLINE.getCode(), proxy.getStatusCode());
|
||||
//
|
||||
// // 发送心跳,更新版本和状态
|
||||
// validProxyDTO.setId(existingProxyId);
|
||||
// validProxyDTO.setVersion("2.0.0");
|
||||
// validProxyDTO.setStatus(MachineProxyStatus.ONLINE.getMessage());
|
||||
// machineProxyService.heartbeat(validProxyDTO);
|
||||
//
|
||||
// // 验证结果
|
||||
// proxy = machineProxyMapper.selectById(existingProxyId);
|
||||
// assertEquals("2.0.0", proxy.getVersion());
|
||||
// assertEquals(MachineProxyStatus.ONLINE.getCode(), proxy.getStatusCode());
|
||||
// }
|
||||
//
|
||||
// // ============================== 状态统计测试 ==============================
|
||||
// @Test
|
||||
// void testGetStatusStatistics_ShouldReturnValidCounts() {
|
||||
// // 假设测试数据中有 OFFLINE(1)、INSTALLING(1)、ONLINE(1) 三种状态
|
||||
// Map<String, Long> stats = machineProxyService.getStatusStatistics();
|
||||
//
|
||||
// // 验证统计结果
|
||||
// assertEquals(3, stats.size());
|
||||
// assertTrue(stats.containsKey(MachineProxyStatus.OFFLINE.getMessage()));
|
||||
// assertTrue(stats.containsKey(MachineProxyStatus.INSTALLING.getMessage()));
|
||||
// assertTrue(stats.containsKey(MachineProxyStatus.ONLINE.getMessage()));
|
||||
// assertEquals(1L, stats.get(MachineProxyStatus.OFFLINE.getMessage()));
|
||||
// }
|
||||
//
|
||||
// // ============================== 更新配置测试 ==============================
|
||||
// @Test
|
||||
// void testUpdateConfig_ValidConfig_ShouldSucceed() {
|
||||
// // 准备数据:现有代理配置为空
|
||||
// MachineProxy proxy = machineProxyMapper.selectById(existingProxyId);
|
||||
// assertNull(proxy.getConfig());
|
||||
//
|
||||
// // 更新配置
|
||||
// validProxyDTO.setId(existingProxyId);
|
||||
// validProxyDTO.setConfig("{\"port\": 8080}");
|
||||
// boolean result = machineProxyService.updateConfig(validProxyDTO);
|
||||
//
|
||||
// // 验证结果
|
||||
// assertTrue(result);
|
||||
// proxy = machineProxyMapper.selectById(existingProxyId);
|
||||
// assertEquals("{\"port\": 8080}", proxy.getConfig());
|
||||
// }
|
||||
//
|
||||
// // ============================== 删除代理测试 ==============================
|
||||
// @Test
|
||||
// void testDelete_OfflineProxy_ShouldDeleteSuccessfully() {
|
||||
// // 准备数据:状态为 OFFLINE 的代理 ID
|
||||
// List<Long> ids = Collections.singletonList(existingProxyId);
|
||||
// machineProxyService.delete(ids);
|
||||
//
|
||||
// // 验证删除
|
||||
// MachineProxy proxy = machineProxyMapper.selectById(existingProxyId);
|
||||
// assertNull(proxy);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testDelete_OnlineProxy_ShouldThrowException() {
|
||||
// // 先将代理状态改为 ONLINE
|
||||
// MachineProxy onlineProxy = new MachineProxy();
|
||||
// onlineProxy.setId(existingProxyId);
|
||||
// onlineProxy.setStatusCode(MachineProxyStatus.ONLINE.getCode());
|
||||
// machineProxyMapper.updateById(onlineProxy);
|
||||
//
|
||||
// // 执行删除
|
||||
// List<Long> ids = Collections.singletonList(existingProxyId);
|
||||
// assertThrows(IllegalArgumentException.class, () -> {
|
||||
// machineProxyService.delete(ids);
|
||||
// }, "以下代理处于在线状态,无法删除: 1");
|
||||
// }
|
||||
//
|
||||
// // ============================== 列表查询测试 ==============================
|
||||
// @Test
|
||||
// void testList_WithStatusFilter_ShouldReturnMatchedRecords() {
|
||||
// // 查询状态为 OFFLINE 的代理
|
||||
// MachineProxyDTO queryDto = new MachineProxyDTO();
|
||||
// queryDto.setStatus(MachineProxyStatus.OFFLINE.getMessage());
|
||||
//
|
||||
// PageResult<MachineProxyDTO> pageResult = machineProxyService.list(queryDto);
|
||||
//
|
||||
// // 验证结果
|
||||
// assertFalse(pageResult.getList().isEmpty());
|
||||
// pageResult.getList().forEach(dto ->
|
||||
// assertEquals(MachineProxyStatus.OFFLINE.getMessage(), dto.getStatus())
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testList_WithHostIpFilter_ShouldReturnMatchedRecords() {
|
||||
// // 假设测试数据中存在 host_ip 为 "192.168.1.1" 的代理
|
||||
// MachineProxyDTO queryDto = new MachineProxyDTO();
|
||||
// queryDto.setHostIp("192.168.1.1");
|
||||
//
|
||||
// PageResult<MachineProxyDTO> pageResult = machineProxyService.list(queryDto);
|
||||
//
|
||||
// // 验证结果
|
||||
// assertFalse(pageResult.getList().isEmpty());
|
||||
// pageResult.getList().forEach(dto ->
|
||||
// assertEquals("192.168.1.1", dto.getHostIp())
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// // ============================== 辅助方法测试 ==============================
|
||||
// @Test
|
||||
// void testEnumUtils_ConvertCodeToMessage() {
|
||||
// // 验证代理类型枚举转换
|
||||
// String typeMessage = EnumUtils.getEnumByCode(MachineProxyType.HTTP.getCode(), MachineProxyType.class).getMessage();
|
||||
// assertEquals("HTTP", typeMessage);
|
||||
//
|
||||
// // 验证状态枚举转换
|
||||
// String statusMessage = EnumUtils.getEnumByCode(MachineProxyStatus.INSTALLING.getCode(), MachineProxyStatus.class).getMessage();
|
||||
// assertEquals("安装中", statusMessage);
|
||||
// }
|
||||
//}
|
@ -0,0 +1,176 @@
|
||||
//package com.casic.machine.service.impl;
|
||||
//
|
||||
//import com.casic.commons.exception.ServiceException;
|
||||
//import com.casic.machine.dto.MachineInfoDto;
|
||||
//import com.casic.machine.entity.MachineInfo;
|
||||
//import com.casic.machine.enums.AuthenticationType;
|
||||
//import com.casic.machine.enums.ConnectionStatus;
|
||||
//import com.casic.machine.enums.MachineInfoStatus;
|
||||
//import org.junit.jupiter.api.BeforeEach;
|
||||
//import org.junit.jupiter.api.Test;
|
||||
//import org.springframework.beans.BeanUtils;
|
||||
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
//import org.springframework.boot.test.context.SpringBootTest;
|
||||
//import org.springframework.test.annotation.Rollback;
|
||||
//import org.springframework.test.context.jdbc.Sql;
|
||||
//import org.springframework.transaction.annotation.Transactional;
|
||||
//
|
||||
//import java.util.Collections;
|
||||
//
|
||||
//import static org.junit.jupiter.api.Assertions.*;
|
||||
//
|
||||
//@SpringBootTest
|
||||
//@Transactional
|
||||
//@Rollback(true)
|
||||
//@Sql(scripts = {"classpath:sql/machine_info_test_data.sql"})
|
||||
//public class MachineinfoServiceImplTest {
|
||||
//
|
||||
// @Autowired
|
||||
// private MachineinfoServiceImpl machineInfoService;
|
||||
//
|
||||
// private MachineInfoDto validMachineInfoDto;
|
||||
// private Long existingMachineId;
|
||||
// private Long nonExistingMachineId;
|
||||
//
|
||||
// @BeforeEach
|
||||
// public void setUp() {
|
||||
// // 初始化测试数据(假设 SQL 脚本已插入一条状态为 ENABLE 的机器)
|
||||
// existingMachineId = 1L;
|
||||
// nonExistingMachineId = 999L;
|
||||
//
|
||||
// // 有效机器信息 DTO
|
||||
// validMachineInfoDto = new MachineInfoDto();
|
||||
// validMachineInfoDto.setName("Test Machine");
|
||||
// validMachineInfoDto.setHostIp("192.168.1.101");
|
||||
// validMachineInfoDto.setSshPort("22");
|
||||
// validMachineInfoDto.setUsername("testuser");
|
||||
// validMachineInfoDto.setAuthenticationType(AuthenticationType.PASSWORD.getMessage());
|
||||
// validMachineInfoDto.setStatus(MachineInfoStatus.ENABLE.getMessage());
|
||||
// }
|
||||
//
|
||||
// // ======================= 新增机器测试 =======================
|
||||
// @Test
|
||||
// void testAddMachineInfo_ValidData_ShouldSucceed() {
|
||||
// boolean result = machineInfoService.addMachineInfo(validMachineInfoDto);
|
||||
// assertTrue(result);
|
||||
//
|
||||
// // 验证数据库存在记录
|
||||
// MachineInfo machineInfo = machineInfoService.getById(validMachineInfoDto.getId());
|
||||
// assertNotNull(machineInfo);
|
||||
// assertEquals(validMachineInfoDto.getHostIp(), machineInfo.getHostIp());
|
||||
// assertEquals(AuthenticationType.PASSWORD.getCode(), machineInfo.getAuthenticationTypeCode());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testAddMachineInfo_NullDto_ShouldThrowException() {
|
||||
// assertThrows(ServiceException.class, () -> {
|
||||
// machineInfoService.addMachineInfo(null);
|
||||
// }, "机器信息为空");
|
||||
// }
|
||||
//
|
||||
// // ======================= 列表查询测试 =======================
|
||||
// @Test
|
||||
// void testListMachineInfo_WithStatusFilter_ShouldReturnValidRecords() {
|
||||
// MachineInfoDto queryDto = new MachineInfoDto();
|
||||
// queryDto.setStatus(MachineInfoStatus.ENABLE.getMessage());
|
||||
//
|
||||
// var pageResult = machineInfoService.listMachineInfo(queryDto);
|
||||
// assertFalse(pageResult.getList().isEmpty());
|
||||
// pageResult.getList().forEach(dto ->
|
||||
// assertEquals(MachineInfoStatus.ENABLE.getMessage(), dto.getStatus())
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testListMachineInfo_WithHostIpFilter_ShouldFilterRecords() {
|
||||
// MachineInfoDto queryDto = new MachineInfoDto();
|
||||
// queryDto.setHostIp("192.168.1.100"); // 假设测试数据中的 IP
|
||||
//
|
||||
// var pageResult = machineInfoService.listMachineInfo(queryDto);
|
||||
// assertFalse(pageResult.getList().isEmpty());
|
||||
// pageResult.getList().forEach(dto ->
|
||||
// assertTrue(dto.getHostIp().contains("192.168.1.100"))
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// // ======================= 更新机器状态测试 =======================
|
||||
// @Test
|
||||
// void testUpdateStatus_ValidId_ShouldUpdateStatus() {
|
||||
// boolean result = machineInfoService.updateStatus(existingMachineId, MachineInfoStatus.UN_ENABLE.getMessage());
|
||||
// assertTrue(result);
|
||||
//
|
||||
// MachineInfo machineInfo = machineInfoService.getById(existingMachineId);
|
||||
// assertEquals(MachineInfoStatus.UN_ENABLE.getCode(), machineInfo.getStatus());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testUpdateStatus_NonExistingId_ShouldFail() {
|
||||
// boolean result = machineInfoService.updateStatus(nonExistingMachineId, MachineInfoStatus.UN_ENABLE.getMessage());
|
||||
// assertFalse(result);
|
||||
// }
|
||||
//
|
||||
// // ======================= 连接测试 =======================
|
||||
// @Test
|
||||
// void testTestConnection_EnabledMachine_ShouldSucceed() {
|
||||
// MachineInfo machineInfo = machineInfoService.getById(existingMachineId);
|
||||
// assertTrue(machineInfoService.testConnection(machineInfo));
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testTestConnection_DisabledMachine_ShouldThrowException() {
|
||||
// // 先禁用机器
|
||||
// machineInfoService.updateStatus(existingMachineId, MachineInfoStatus.UN_ENABLE.getMessage());
|
||||
// MachineInfo disabledMachine = machineInfoService.getById(existingMachineId);
|
||||
//
|
||||
// assertThrows(RuntimeException.class, () -> {
|
||||
// machineInfoService.testConnection(disabledMachine);
|
||||
// }, "机器不可用");
|
||||
// }
|
||||
//
|
||||
// // ======================= 会话管理测试 =======================
|
||||
// @Test
|
||||
// void testConnect_NewMachine_ShouldCreateSession() {
|
||||
// MachineInfo machineInfo = machineInfoService.getById(existingMachineId);
|
||||
// String sessionId = machineInfoService.connect(machineInfo);
|
||||
//
|
||||
// assertNotNull(sessionId);
|
||||
// assertTrue(sessionId.startsWith("session-"));
|
||||
// assertEquals(ConnectionStatus.CONNECTING, machineInfoService.getConnectionStatus(machineInfo.getName()));
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
// // ======================= 删除机器测试 =======================
|
||||
// @Test
|
||||
// void testDeleteMachineInfo_EnabledMachine_ShouldDelete() {
|
||||
// machineInfoService.deleteMachineInfo(existingMachineId);
|
||||
// assertNull(machineInfoService.getById(existingMachineId));
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testDeleteList_ValidIds_ShouldDeleteBatch() {
|
||||
// machineInfoService.deleteList(Collections.singletonList(existingMachineId));
|
||||
// var list = machineInfoService.list();
|
||||
// assertFalse(list.contains(existingMachineId));
|
||||
// }
|
||||
//
|
||||
// // ======================= 辅助功能测试 =======================
|
||||
// @Test
|
||||
// void testAuthenticationTypeConversion() {
|
||||
// validMachineInfoDto.setAuthenticationType(AuthenticationType.SECRET_KEY.getMessage());
|
||||
// MachineInfo machineInfo = new MachineInfo();
|
||||
// BeanUtils.copyProperties(validMachineInfoDto, machineInfo);
|
||||
//
|
||||
// assertEquals(AuthenticationType.SECRET_KEY.getCode(), machineInfo.getAuthenticationTypeCode());
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testGetAllConnectionStatus_ShouldReturnStatusMap() {
|
||||
// MachineInfo machineInfo = machineInfoService.getById(existingMachineId);
|
||||
// machineInfoService.connect(machineInfo);
|
||||
//
|
||||
// var statusMap = machineInfoService.getAllConnectionStatus();
|
||||
// assertFalse(statusMap.isEmpty());
|
||||
// assertTrue(statusMap.containsValue(ConnectionStatus.CONNECTING));
|
||||
// }
|
||||
//}
|
@ -0,0 +1,218 @@
|
||||
//package com.casic.machine.service.impl;
|
||||
//
|
||||
//import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
//import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
//import com.casic.commons.exception.ServiceException;
|
||||
//import com.casic.commons.utils.AliOssUtil;
|
||||
//import com.casic.machine.entity.MachineInfo;
|
||||
//import com.casic.machine.entity.SecretKey;
|
||||
//import com.casic.machine.dto.SecretKeyDto;
|
||||
//import com.casic.machine.mapper.SecretServiceMapper;
|
||||
//import com.casic.machine.service.MachineInfoService;
|
||||
//import com.jayway.jsonpath.internal.Utils;
|
||||
//import org.junit.jupiter.api.BeforeEach;
|
||||
//import org.junit.jupiter.api.Test;
|
||||
//import org.mockito.InjectMocks;
|
||||
//import org.mockito.Mock;
|
||||
//import org.mockito.MockitoAnnotations;
|
||||
//import org.springframework.beans.BeanUtils;
|
||||
//import org.springframework.core.io.InputStreamResource;
|
||||
//import org.springframework.http.ResponseEntity;
|
||||
//import org.springframework.mock.web.MockMultipartFile;
|
||||
//import org.springframework.test.util.ReflectionTestUtils;
|
||||
//
|
||||
//import java.io.ByteArrayInputStream;
|
||||
//import java.io.IOException;
|
||||
//import java.util.Collections;
|
||||
//import java.util.List;
|
||||
//
|
||||
//import static org.junit.jupiter.api.Assertions.*;
|
||||
//import static org.mockito.ArgumentMatchers.any;
|
||||
//import static org.mockito.Mockito.*;
|
||||
//
|
||||
//@SuppressWarnings("unchecked")
|
||||
//public class SecretKeyServiceImplTest {
|
||||
//
|
||||
// @InjectMocks
|
||||
// private SecretKeyServiceImpl secretKeyService;
|
||||
//
|
||||
// @Mock
|
||||
// private AliOssUtil aliOssUtil;
|
||||
//
|
||||
// @Mock
|
||||
// private SecretServiceMapper secretServiceMapper;
|
||||
//
|
||||
// @Mock
|
||||
// private MachineInfoService machineInfoService;
|
||||
//
|
||||
// private SecretKeyDto validSecretKeyDto;
|
||||
// private MockMultipartFile mockFile;
|
||||
// private final Long TEST_SECRET_KEY_ID = 1L;
|
||||
// private final String TEST_FILE_NAME = "test_key.pem";
|
||||
// private final String TEST_PATH = "https://bucket.endpoint/test_key.pem";
|
||||
//
|
||||
// @BeforeEach
|
||||
// public void setUp() throws IOException {
|
||||
// MockitoAnnotations.openMocks(this);
|
||||
// validSecretKeyDto = new SecretKeyDto();
|
||||
// validSecretKeyDto.setName("Test Key");
|
||||
// validSecretKeyDto.setDescription("Test secret key");
|
||||
//
|
||||
// // 创建模拟文件
|
||||
// byte[] fileContent = "test content".getBytes();
|
||||
// mockFile = new MockMultipartFile(
|
||||
// "file",
|
||||
// TEST_FILE_NAME,
|
||||
// "application/octet-stream",
|
||||
// new ByteArrayInputStream(fileContent)
|
||||
// );
|
||||
//
|
||||
// // 模拟 OSS 工具返回值
|
||||
// when(aliOssUtil.save(any(MockMultipartFile.class))).thenReturn(TEST_FILE_NAME);
|
||||
//
|
||||
// }
|
||||
//
|
||||
// // ======================= 新增密钥测试 =======================
|
||||
// @Test
|
||||
// void testAddSecretKey_ValidData_ShouldSucceed() throws IOException {
|
||||
// // 执行新增
|
||||
// boolean result = secretKeyService.addSecretKey(validSecretKeyDto, mockFile);
|
||||
//
|
||||
// // 验证 OSS 保存调用
|
||||
// verify(aliOssUtil, times(1)).save(mockFile);
|
||||
//
|
||||
// // 验证实体属性
|
||||
// SecretKey savedKey = new SecretKey();
|
||||
// BeanUtils.copyProperties(validSecretKeyDto, savedKey);
|
||||
// savedKey.setFileName(TEST_FILE_NAME);
|
||||
// savedKey.setPath(TEST_PATH);
|
||||
//
|
||||
// // 验证 Mapper 调用
|
||||
// verify(secretServiceMapper, times(1)).insert(savedKey);
|
||||
// assertTrue(result);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// void testAddSecretKey_NullFile_ShouldThrowException() {
|
||||
// assertThrows(ServiceException.class, () -> {
|
||||
// secretKeyService.addSecretKey(validSecretKeyDto, null);
|
||||
// }, "文件为空");
|
||||
// }
|
||||
//
|
||||
// // ======================= 绑定机器测试 =======================
|
||||
// @Test
|
||||
// void testBindingMachine_ValidIds_ShouldUpdateMachine() {
|
||||
// // 模拟机器列表
|
||||
// List<Long> machineIds = Collections.singletonList(1L);
|
||||
// when(machineInfoService.listByIds(machineIds)).thenReturn(Collections.singletonList(new MachineInfo()));
|
||||
//
|
||||
// secretKeyService.bindingMachine(TEST_SECRET_KEY_ID, machineIds);
|
||||
//
|
||||
// // 验证机器信息更新
|
||||
// verify(machineInfoService, times(1)).listByIds(machineIds);
|
||||
// machineIds.forEach(id -> {
|
||||
// verify(machineInfoService, times(1)).update(any());
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// // ======================= 更新密钥测试 =======================
|
||||
// @Test
|
||||
// void testUpdateSecretKey_WithNewFile_ShouldUpdatePath() throws IOException {
|
||||
// MockMultipartFile newFile = new MockMultipartFile(
|
||||
// "file",
|
||||
// "new_key.pem",
|
||||
// "application/octet-stream",
|
||||
// new ByteArrayInputStream("new content".getBytes())
|
||||
// );
|
||||
// when(aliOssUtil.save(newFile)).thenReturn("new_key.pem");
|
||||
//
|
||||
// validSecretKeyDto.setId(TEST_SECRET_KEY_ID);
|
||||
// boolean result = secretKeyService.updateSecretKey(validSecretKeyDto, newFile);
|
||||
//
|
||||
// // 验证 OSS 调用和路径更新
|
||||
// verify(aliOssUtil, times(1)).save(newFile);
|
||||
// SecretKey updatedKey = new SecretKey();
|
||||
// BeanUtils.copyProperties(validSecretKeyDto, updatedKey);
|
||||
// updatedKey.setFileName("new_key.pem");
|
||||
// updatedKey.setPath("https://bucket.endpoint/new_key.pem");
|
||||
// verify(secretServiceMapper, times(1)).updateById(updatedKey);
|
||||
// assertTrue(result);
|
||||
// }
|
||||
//
|
||||
// // ======================= 删除密钥测试 =======================
|
||||
// @Test
|
||||
// void testDeleteSecretKey_ValidId_ShouldDeleteFileAndRecord() {
|
||||
// SecretKey secretKey = new SecretKey();
|
||||
// secretKey.setId(TEST_SECRET_KEY_ID);
|
||||
// secretKey.setFileName(TEST_FILE_NAME);
|
||||
// when(secretServiceMapper.selectById(TEST_SECRET_KEY_ID)).thenReturn(secretKey);
|
||||
//
|
||||
// boolean result = secretKeyService.deleteSecretKey(TEST_SECRET_KEY_ID);
|
||||
//
|
||||
// // 验证 OSS 删除和 Mapper 调用
|
||||
// verify(aliOssUtil, times(1)).deleteFile(TEST_FILE_NAME);
|
||||
// verify(secretServiceMapper, times(1)).deleteById(TEST_SECRET_KEY_ID);
|
||||
// assertTrue(result);
|
||||
// }
|
||||
//
|
||||
// // ======================= 列表查询测试 =======================
|
||||
// @Test
|
||||
// void testListSecretKey_WithNameFilter_ShouldReturnMatchedRecords() {
|
||||
// SecretKeyDto queryDto = new SecretKeyDto();
|
||||
// queryDto.setName("Test");
|
||||
// QueryWrapper<SecretKey> wrapper = new QueryWrapper<>();
|
||||
// wrapper.like("name", "Test");
|
||||
//
|
||||
// when(secretServiceMapper.selectPage(any(), any())).thenReturn(new Page<>());
|
||||
//
|
||||
// secretKeyService.listSecretKey(queryDto);
|
||||
// verify(secretServiceMapper, times(1)).selectPage(any(), wrapper);
|
||||
// }
|
||||
//
|
||||
// // ======================= 文件下载测试 =======================
|
||||
// @Test
|
||||
// void testDownloadSecretKeyFile_ValidId_ShouldReturnResponseEntity() throws IOException {
|
||||
// SecretKey secretKey = new SecretKey();
|
||||
// secretKey.setFileName(TEST_FILE_NAME);
|
||||
// when(secretServiceMapper.selectById(TEST_SECRET_KEY_ID)).thenReturn(secretKey);
|
||||
// InputStreamResource inputStreamResource = new InputStreamResource(
|
||||
// new ByteArrayInputStream("test content".getBytes())
|
||||
// );
|
||||
// when(aliOssUtil.downloadFile(TEST_FILE_NAME)).thenReturn(
|
||||
// ResponseEntity.ok(inputStreamResource)
|
||||
// );
|
||||
//
|
||||
// ResponseEntity<InputStreamResource> response = secretKeyService.downloadSecretKeyFile(TEST_SECRET_KEY_ID);
|
||||
// assertNotNull(response);
|
||||
// assertEquals("test content", Utils.toString(response.getBody().getInputStream()));
|
||||
// }
|
||||
//
|
||||
// // ======================= 批量删除测试 =======================
|
||||
// @Test
|
||||
// void testDeleteList_ValidIds_ShouldSubmitAsyncDelete() {
|
||||
// List<Long> ids = Collections.singletonList(TEST_SECRET_KEY_ID);
|
||||
// SecretKey secretKey = new SecretKey();
|
||||
// secretKey.setFileName(TEST_FILE_NAME);
|
||||
// when(secretServiceMapper.selectBatchIds(ids)).thenReturn(Collections.singletonList(secretKey));
|
||||
//
|
||||
// secretKeyService.deleteList(ids);
|
||||
//
|
||||
// // 验证异步任务提交
|
||||
// verify(secretKeyService.FILE_DELETE_EXECUTOR, times(1)).execute(any(Runnable.class));
|
||||
// verify(secretServiceMapper, times(1)).deleteBatchIds(ids);
|
||||
// }
|
||||
//
|
||||
// // ======================= 异常处理测试 =======================
|
||||
// @Test
|
||||
// void testDeleteSecretKey_FileDeleteFailed_ShouldThrowException() {
|
||||
// aliOssUtil.deleteFile(TEST_FILE_NAME);
|
||||
// SecretKey secretKey = new SecretKey();
|
||||
// secretKey.setId(TEST_SECRET_KEY_ID);
|
||||
// secretKey.setFileName(TEST_FILE_NAME);
|
||||
// when(secretServiceMapper.selectById(TEST_SECRET_KEY_ID)).thenReturn(secretKey);
|
||||
//
|
||||
// assertThrows(ServiceException.class, () -> {
|
||||
// secretKeyService.deleteSecretKey(TEST_SECRET_KEY_ID);
|
||||
// }, "删除文件失败");
|
||||
// }
|
||||
//}
|
@ -14,6 +14,7 @@
|
||||
<module>module-infra-biz</module>
|
||||
<module>module-system-api</module>
|
||||
<module>module-system-biz</module>
|
||||
<module>module-ci-machine</module>
|
||||
</modules>
|
||||
|
||||
<artifactId>modules</artifactId>
|
||||
|
@ -25,6 +25,11 @@
|
||||
<artifactId>module-system-biz</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>module-ci-machine</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
|
@ -156,3 +156,12 @@ logging:
|
||||
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # 禁用,Spring Boot 3.X 存在部分错误的 WARN 提示
|
||||
|
||||
debug: false
|
||||
|
||||
|
||||
#阿里云oss
|
||||
aliyun:
|
||||
oss:
|
||||
endpoint: https://oss-cn-beijing.aliyuncs.com
|
||||
accessKeyId: LTAI5tPCKES4ZxdRKybGjJf4
|
||||
accessKeySecret: mtX94qmxnWR2FDem0z38qjv0rrILSE
|
||||
bucketName: kkt-web-tlias
|
||||
|
Loading…
x
Reference in New Issue
Block a user