目标上传worker添加、测试用例生成worker 、 AFLworker
This commit is contained in:
parent
4029e7d497
commit
eb7b06607c
@ -14,7 +14,7 @@ public class MachineInfoResp {
|
|||||||
* id
|
* id
|
||||||
*/
|
*/
|
||||||
@TableId(type = IdType.ASSIGN_ID)
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
private Long id;
|
private String id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主机ip
|
* 主机ip
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package cd.casic.ci.process.engine.constant;
|
||||||
|
|
||||||
|
public class AFLConstant {
|
||||||
|
/**
|
||||||
|
* 脚本内容
|
||||||
|
*/
|
||||||
|
public static final String COMMAND_SCRIPT ="buildScript";
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package cd.casic.ci.process.engine.constant;
|
||||||
|
|
||||||
|
public class TestCaseGenerationConstant {
|
||||||
|
/**
|
||||||
|
* 脚本内容key
|
||||||
|
*/
|
||||||
|
public static final String COMMAND_SCRIPT ="buildScript";
|
||||||
|
}
|
@ -32,6 +32,7 @@ public class MemoryLogManager implements LoggerManager {
|
|||||||
private final Map<String,StringBuffer> taskIdMemoryLogMap = new ConcurrentHashMap<>();
|
private final Map<String,StringBuffer> taskIdMemoryLogMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public final Integer FLUSH_DB_SIZE=2*1024*1024;
|
public final Integer FLUSH_DB_SIZE=2*1024*1024;
|
||||||
|
// public final Integer FLUSH_DB_SIZE=1000;
|
||||||
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||||
/**
|
/**
|
||||||
* 缓存最近一次执行的日志,key是taskId,val是数据库id(已入库的情况下)用于buffer满了增加日志内容
|
* 缓存最近一次执行的日志,key是taskId,val是数据库id(已入库的情况下)用于buffer满了增加日志内容
|
||||||
|
@ -19,8 +19,8 @@ import java.util.List;
|
|||||||
* @Date: 2025/5/26 17:28
|
* @Date: 2025/5/26 17:28
|
||||||
* @Description:
|
* @Description:
|
||||||
*/
|
*/
|
||||||
@Component
|
//@Component
|
||||||
@RequiredArgsConstructor
|
//@RequiredArgsConstructor
|
||||||
public class PipelineSchedulingBootstrapper {
|
public class PipelineSchedulingBootstrapper {
|
||||||
@Resource
|
@Resource
|
||||||
private PipelineSchedulingPropertiesServiceImpl taskService;
|
private PipelineSchedulingPropertiesServiceImpl taskService;
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
package cd.casic.ci.process.engine.worker;
|
package cd.casic.ci.process.engine.worker;
|
||||||
|
|
||||||
import cd.casic.ci.common.pipeline.annotation.Plugin;
|
import cd.casic.ci.common.pipeline.annotation.Plugin;
|
||||||
|
import cd.casic.ci.process.engine.constant.AFLConstant;
|
||||||
|
import cd.casic.ci.process.engine.constant.DIYImageExecuteCommandConstant;
|
||||||
|
import cd.casic.ci.process.engine.constant.EngineRuntimeConstant;
|
||||||
|
import cd.casic.ci.process.engine.runContext.BaseRunContext;
|
||||||
|
import cd.casic.ci.process.engine.runContext.PipelineRunContext;
|
||||||
import cd.casic.ci.process.engine.runContext.TaskRunContext;
|
import cd.casic.ci.process.engine.runContext.TaskRunContext;
|
||||||
import cd.casic.ci.process.process.dataObject.base.PipBaseElement;
|
import cd.casic.ci.process.process.dataObject.base.PipBaseElement;
|
||||||
|
import cd.casic.ci.process.process.dataObject.log.PipTaskLog;
|
||||||
|
import cd.casic.ci.process.process.dataObject.machine.MachineInfo;
|
||||||
import cd.casic.ci.process.process.dataObject.pipeline.PipPipeline;
|
import cd.casic.ci.process.process.dataObject.pipeline.PipPipeline;
|
||||||
import cd.casic.ci.process.process.dataObject.target.TargetVersion;
|
import cd.casic.ci.process.process.dataObject.target.TargetVersion;
|
||||||
import cd.casic.ci.process.process.dataObject.task.PipTask;
|
import cd.casic.ci.process.process.dataObject.task.PipTask;
|
||||||
import cd.casic.ci.process.process.service.target.TargetVersionService;
|
import cd.casic.ci.process.process.service.target.TargetVersionService;
|
||||||
|
import cd.casic.ci.process.util.CryptogramUtil;
|
||||||
import cd.casic.framework.commons.exception.ServiceException;
|
import cd.casic.framework.commons.exception.ServiceException;
|
||||||
import cd.casic.framework.commons.exception.enums.GlobalErrorCodeConstants;
|
import cd.casic.framework.commons.exception.enums.GlobalErrorCodeConstants;
|
||||||
import com.jcraft.jsch.*;
|
import com.jcraft.jsch.*;
|
||||||
@ -21,121 +29,50 @@ import java.util.Map;
|
|||||||
|
|
||||||
@Plugin(taskType = "AFL")
|
@Plugin(taskType = "AFL")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class AFLWorker extends SshWorker{
|
public class AFLWorker extends SshWorker {
|
||||||
@Resource
|
|
||||||
private TargetVersionService targetVersionService;
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(TaskRunContext context) {
|
public void execute(TaskRunContext context) {
|
||||||
String filePath = "";
|
int statusCode = -1;
|
||||||
Map<String, Object> localVariables = context.getLocalVariables();
|
Map<String, Object> localVariables = context.getLocalVariables();
|
||||||
PipBaseElement taskContextDef = context.getContextDef();
|
if (context.getContextDef() instanceof PipTask taskDef) {
|
||||||
if (taskContextDef instanceof PipTask pipTask){
|
log.info(taskDef.getTaskName());
|
||||||
// 查询并下载目标文件
|
Map<String, Object> taskProperties = taskDef.getTaskProperties();
|
||||||
String pipelineId = pipTask.getPipelineId();
|
Object commandScriptObj = taskProperties.get(AFLConstant.COMMAND_SCRIPT);
|
||||||
//根据流水线id查询流水线信息
|
// Object machineIdObj = taskProperties.get(DIYImageExecuteCommandConstant.MACHINE_ID);
|
||||||
PipPipeline pipeline = (PipPipeline) getContextManager().getContext(pipelineId).getContextDef();
|
String commandScript = commandScriptObj instanceof String ? ((String) commandScriptObj) : null;
|
||||||
//根据目标id查询目标信息
|
|
||||||
if (StringUtils.isEmpty(pipeline.getTargetVersionId())){
|
PipPipeline pipeline = (PipPipeline) getContextManager().getContext(taskDef.getPipelineId()).getContextDef();
|
||||||
throw new ServiceException(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),"目标文件不存在");
|
String machineId = pipeline.getMachineId();
|
||||||
}
|
if (StringUtils.isEmpty(commandScript)||StringUtils.isEmpty(machineId)) {
|
||||||
TargetVersion targetVersion = targetVersionService.getById(pipeline.getTargetVersionId());
|
// 缺少参数
|
||||||
filePath = targetVersion.getFilePath();
|
|
||||||
File file = new File(filePath);
|
|
||||||
if (!file.exists() || !file.canRead()) {
|
|
||||||
log.error("目标文件不存在或不可读");
|
|
||||||
localVariables.put("statusCode", "-1");
|
|
||||||
append(context,"目标文件不存在或不可读");
|
|
||||||
toBadEnding();
|
toBadEnding();
|
||||||
}
|
}
|
||||||
// 上传文件
|
|
||||||
// 执行shell脚本
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static void main(String[] args) {
|
|
||||||
String remoteHost = "your-remote-server-ip-or-hostname"; // 远程服务器IP或主机名
|
|
||||||
int remotePort = 22; // SFTP 默认端口通常是 22 (SSH 端口)
|
|
||||||
String username = "your-username"; // 远程服务器用户名
|
|
||||||
String password = "your-password"; // 远程服务器密码 或 SSH Key 路径
|
|
||||||
|
|
||||||
String localFilePath = "path/to/your/local/file.txt"; // 要上传的本地文件路径
|
|
||||||
String remoteDir = "/path/on/remote/server/"; // 远程服务器存放文件的目录
|
|
||||||
String remoteFileName = "uploaded_file.txt"; // 上传到远程服务器的文件名 (可以和本地不同)
|
|
||||||
|
|
||||||
Session session = null;
|
|
||||||
Channel channel = null;
|
|
||||||
ChannelSftp channelSftp = null;
|
|
||||||
FileInputStream fis = null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JSch jsch = new JSch();
|
//将节点的配置信息反编译成对象
|
||||||
|
log.info("构建脚本" + commandScript);
|
||||||
|
|
||||||
// 如果使用 SSH Key 认证,可以添加身份文件:
|
//如果machineId为0,则说明该节点没有配置机器,则使用开始节点的机器
|
||||||
// jsch.addIdentity("/path/to/your/private/key"); // 替换为你的私钥文件路径
|
|
||||||
|
|
||||||
session = jsch.getSession(username, remoteHost, remotePort);
|
//获取机器
|
||||||
|
MachineInfo machineInfoDO = this.getMachineInfoService().getById(machineId);
|
||||||
// 如果使用密码认证:
|
statusCode = shell(machineInfoDO, CryptogramUtil.doDecrypt(machineInfoDO.getPassword()), context,
|
||||||
session.setPassword(password);
|
"echo \"自定义镜像执行命令\"",
|
||||||
|
commandScript
|
||||||
// 设置连接不进行主机密钥检查 (生产环境不推荐,应该配置known_hosts)
|
);
|
||||||
java.util.Properties config = new java.util.Properties();
|
} catch (Exception e) {
|
||||||
config.put("StrictHostKeyChecking", "no");
|
String errorMessage = "该节点配置信息为空,请先配置该节点信息" + "\r\n";
|
||||||
session.setConfig(config);
|
log.error("执行ssh失败:", e);
|
||||||
|
append(context, errorMessage);
|
||||||
session.connect();
|
toBadEnding();
|
||||||
System.out.println("SFTP Session 连接成功.");
|
|
||||||
|
|
||||||
channel = session.openChannel("sftp");
|
|
||||||
channel.connect();
|
|
||||||
System.out.println("SFTP Channel 打开成功.");
|
|
||||||
|
|
||||||
channelSftp = (ChannelSftp) channel;
|
|
||||||
|
|
||||||
// 切换到远程目录 (如果目录不存在,可能需要先创建)
|
|
||||||
try {
|
|
||||||
channelSftp.cd(remoteDir);
|
|
||||||
} catch (SftpException e) {
|
|
||||||
System.err.println("远程目录不存在,尝试创建: " + remoteDir);
|
|
||||||
try {
|
|
||||||
channelSftp.mkdir(remoteDir);
|
|
||||||
channelSftp.cd(remoteDir);
|
|
||||||
} catch (SftpException e2) {
|
|
||||||
System.err.println("创建远程目录失败: " + remoteDir);
|
|
||||||
throw e2; // 抛出异常终止上传
|
|
||||||
}
|
}
|
||||||
|
if (statusCode == 0) {
|
||||||
|
log.info("节点执行完成");
|
||||||
|
} else {
|
||||||
|
log.error("节点执行失败");
|
||||||
}
|
}
|
||||||
|
localVariables.put(DIYImageExecuteCommandConstant.STATUS_CODE, statusCode);
|
||||||
|
|
||||||
// 上传文件
|
|
||||||
File localFile = new File(localFilePath);
|
|
||||||
fis = new FileInputStream(localFile);
|
|
||||||
|
|
||||||
channelSftp.put(fis, remoteFileName);
|
|
||||||
System.out.println("文件上传成功到: " + remoteDir + remoteFileName);
|
|
||||||
|
|
||||||
} catch (JSchException | SftpException | IOException e) {
|
|
||||||
System.err.println("SFTP 文件上传过程中发生异常: " + e.getMessage());
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
// 关闭资源
|
|
||||||
if (fis != null) {
|
|
||||||
try {
|
|
||||||
fis.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (channelSftp != null) {
|
|
||||||
channelSftp.disconnect();
|
|
||||||
System.out.println("SFTP Channel 已断开.");
|
|
||||||
}
|
|
||||||
if (channel != null) {
|
|
||||||
channel.disconnect();
|
|
||||||
}
|
|
||||||
if (session != null) {
|
|
||||||
session.disconnect();
|
|
||||||
System.out.println("SFTP Session 已断开.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,23 @@ package cd.casic.ci.process.engine.worker;
|
|||||||
import cd.casic.ci.common.pipeline.annotation.Plugin;
|
import cd.casic.ci.common.pipeline.annotation.Plugin;
|
||||||
import cd.casic.ci.process.engine.runContext.TaskRunContext;
|
import cd.casic.ci.process.engine.runContext.TaskRunContext;
|
||||||
import cd.casic.ci.process.process.dataObject.base.PipBaseElement;
|
import cd.casic.ci.process.process.dataObject.base.PipBaseElement;
|
||||||
|
import cd.casic.ci.process.process.dataObject.machine.MachineInfo;
|
||||||
|
import cd.casic.ci.process.process.dataObject.pipeline.PipPipeline;
|
||||||
|
import cd.casic.ci.process.process.dataObject.target.TargetVersion;
|
||||||
|
import cd.casic.ci.process.process.dataObject.task.PipTask;
|
||||||
|
import cd.casic.ci.process.process.service.machine.MachineInfoService;
|
||||||
|
import cd.casic.ci.process.process.service.target.TargetVersionService;
|
||||||
|
import cd.casic.ci.process.util.CryptogramUtil;
|
||||||
|
import cd.casic.ci.process.util.SftpUploadUtil;
|
||||||
|
import cd.casic.framework.commons.exception.ServiceException;
|
||||||
|
import cd.casic.framework.commons.exception.enums.GlobalErrorCodeConstants;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 目标处理worker
|
* 目标处理worker
|
||||||
@ -13,11 +28,44 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@Plugin(taskType = "code")
|
@Plugin(taskType = "code")
|
||||||
public class TargetHandleWorker extends BaseWorker{
|
public class TargetHandleWorker extends BaseWorker{
|
||||||
|
@Resource
|
||||||
|
private TargetVersionService targetVersionService;
|
||||||
|
@Resource
|
||||||
|
private MachineInfoService machineInfoService;
|
||||||
@Override
|
@Override
|
||||||
public void execute(TaskRunContext context) {
|
public void execute(TaskRunContext context) {
|
||||||
PipBaseElement contextDef = context.getContextDef();
|
String filePath = "";
|
||||||
String id = contextDef.getId();
|
Map<String, Object> localVariables = context.getLocalVariables();
|
||||||
log.info("==============触发worker执行========");
|
PipBaseElement taskContextDef = context.getContextDef();
|
||||||
log.info("==========运行context:{}===========", JSON.toJSONString(context));
|
if (taskContextDef instanceof PipTask pipTask){
|
||||||
|
// 查询并下载目标文件
|
||||||
|
String pipelineId = pipTask.getPipelineId();
|
||||||
|
//根据流水线id查询流水线信息
|
||||||
|
PipPipeline pipeline = (PipPipeline) getContextManager().getContext(pipelineId).getContextDef();
|
||||||
|
//根据目标id查询目标信息
|
||||||
|
if (StringUtils.isEmpty(pipeline.getTargetVersionId())){
|
||||||
|
throw new ServiceException(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),"目标文件不存在");
|
||||||
|
}
|
||||||
|
TargetVersion targetVersion = targetVersionService.getById(pipeline.getTargetVersionId());
|
||||||
|
filePath = targetVersion.getFilePath();
|
||||||
|
File file = new File(filePath);
|
||||||
|
if (!file.exists() || !file.canRead()) {
|
||||||
|
log.error("目标文件不存在或不可读");
|
||||||
|
localVariables.put("statusCode", "-1");
|
||||||
|
append(context,"目标文件不存在或不可读");
|
||||||
|
toBadEnding();
|
||||||
|
}
|
||||||
|
// 上传文件
|
||||||
|
String machineId = pipeline.getMachineId();
|
||||||
|
MachineInfo byId = machineInfoService.getById(machineId);
|
||||||
|
append(context,"开始文件上传");
|
||||||
|
try {
|
||||||
|
SftpUploadUtil.uploadFileViaSftp(byId.getMachineHost(),byId.getSshPort(),byId.getUsername(), CryptogramUtil.doDecrypt(byId.getPassword()),null,file.getAbsolutePath(),"/home/casic/706/ai_test_527",file.getName());
|
||||||
|
} catch (SftpUploadUtil.SftpUploadException e) {
|
||||||
|
log.error("文件上传失败",e);
|
||||||
|
toBadEnding();
|
||||||
|
}
|
||||||
|
append(context,"文件上传至"+byId.getMachineHost()+" /home/casic/706/ai_test_527");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,64 @@
|
|||||||
package cd.casic.ci.process.engine.worker;
|
package cd.casic.ci.process.engine.worker;
|
||||||
|
|
||||||
import cd.casic.ci.common.pipeline.annotation.Plugin;
|
import cd.casic.ci.common.pipeline.annotation.Plugin;
|
||||||
|
import cd.casic.ci.process.engine.constant.AFLConstant;
|
||||||
|
import cd.casic.ci.process.engine.constant.DIYImageExecuteCommandConstant;
|
||||||
|
import cd.casic.ci.process.engine.constant.TestCaseGenerationConstant;
|
||||||
import cd.casic.ci.process.engine.runContext.TaskRunContext;
|
import cd.casic.ci.process.engine.runContext.TaskRunContext;
|
||||||
|
import cd.casic.ci.process.process.dataObject.machine.MachineInfo;
|
||||||
|
import cd.casic.ci.process.process.dataObject.pipeline.PipPipeline;
|
||||||
|
import cd.casic.ci.process.process.dataObject.task.PipTask;
|
||||||
|
import cd.casic.ci.process.util.CryptogramUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Plugin(taskType = "TEST_CASE_GENERATION")
|
@Plugin(taskType = "TEST_CASE_GENERATION")
|
||||||
|
@Slf4j
|
||||||
public class TestCaseGenerationWorker extends SshWorker{
|
public class TestCaseGenerationWorker extends SshWorker{
|
||||||
@Override
|
@Override
|
||||||
public void execute(TaskRunContext context) {
|
public void execute(TaskRunContext context) {
|
||||||
|
int statusCode = -1;
|
||||||
|
Map<String, Object> localVariables = context.getLocalVariables();
|
||||||
|
if (context.getContextDef() instanceof PipTask taskDef) {
|
||||||
|
log.info(taskDef.getTaskName());
|
||||||
|
Map<String, Object> taskProperties = taskDef.getTaskProperties();
|
||||||
|
Object commandScriptObj = taskProperties.get(TestCaseGenerationConstant.COMMAND_SCRIPT);
|
||||||
|
// Object machineIdObj = taskProperties.get(DIYImageExecuteCommandConstant.MACHINE_ID);
|
||||||
|
String commandScript = commandScriptObj instanceof String ? ((String) commandScriptObj) : null;
|
||||||
|
|
||||||
|
PipPipeline pipeline = (PipPipeline) getContextManager().getContext(taskDef.getPipelineId()).getContextDef();
|
||||||
|
String machineId = pipeline.getMachineId();
|
||||||
|
if (StringUtils.isEmpty(commandScript)||StringUtils.isEmpty(machineId)) {
|
||||||
|
// 缺少参数
|
||||||
|
toBadEnding();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//将节点的配置信息反编译成对象
|
||||||
|
log.info("构建脚本" + commandScript);
|
||||||
|
|
||||||
|
//如果machineId为0,则说明该节点没有配置机器,则使用开始节点的机器
|
||||||
|
|
||||||
|
//获取机器
|
||||||
|
MachineInfo machineInfoDO = this.getMachineInfoService().getById(machineId);
|
||||||
|
statusCode = shell(machineInfoDO, CryptogramUtil.doDecrypt(machineInfoDO.getPassword()), context,
|
||||||
|
"echo \"自定义镜像执行命令\"",
|
||||||
|
commandScript
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
String errorMessage = "该节点配置信息为空,请先配置该节点信息" + "\r\n";
|
||||||
|
log.error("执行ssh失败:", e);
|
||||||
|
append(context, errorMessage);
|
||||||
|
toBadEnding();
|
||||||
|
}
|
||||||
|
if (statusCode == 0) {
|
||||||
|
log.info("节点执行完成");
|
||||||
|
} else {
|
||||||
|
log.error("节点执行失败");
|
||||||
|
}
|
||||||
|
localVariables.put(DIYImageExecuteCommandConstant.STATUS_CODE, statusCode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package cd.casic.ci.process.process.dataObject.log;
|
|||||||
|
|
||||||
import cd.casic.ci.process.constant.CommandConstant;
|
import cd.casic.ci.process.constant.CommandConstant;
|
||||||
import cd.casic.framework.commons.dataobject.BaseDO;
|
import cd.casic.framework.commons.dataobject.BaseDO;
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
@ -10,6 +12,7 @@ import lombok.EqualsAndHashCode;
|
|||||||
public class PipTaskLog extends BaseDO {
|
public class PipTaskLog extends BaseDO {
|
||||||
private String taskId;
|
private String taskId;
|
||||||
private String content;
|
private String content;
|
||||||
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
private String id;
|
private String id;
|
||||||
public void append(String content){
|
public void append(String content){
|
||||||
this.content += CommandConstant.ENTER;
|
this.content += CommandConstant.ENTER;
|
||||||
|
@ -34,7 +34,7 @@ public class MachineInfo extends BaseDO {
|
|||||||
* id
|
* id
|
||||||
*/
|
*/
|
||||||
@TableId(type = IdType.ASSIGN_ID)
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
private Long id;
|
private String id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主机ip
|
* 主机ip
|
||||||
|
@ -135,5 +135,8 @@ public class PipPipeline extends PipBaseElement {
|
|||||||
* 运行实例编号
|
* 运行实例编号
|
||||||
*/
|
*/
|
||||||
private String instanceNum;
|
private String instanceNum;
|
||||||
|
/**
|
||||||
|
* 机器id
|
||||||
|
* */
|
||||||
|
private String machineId;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,266 @@
|
|||||||
|
package cd.casic.ci.process.util;
|
||||||
|
|
||||||
|
import cd.casic.framework.commons.exception.ServiceException;
|
||||||
|
import com.jcraft.jsch.*;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class SftpUploadUtil {
|
||||||
|
|
||||||
|
private static final int DEFAULT_SFTP_PORT = 22;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 SFTP 上传文件到远程服务器
|
||||||
|
*
|
||||||
|
* @param remoteHost 远程服务器IP或主机名
|
||||||
|
* @param remotePort 远程服务器端口 (通常是 22),为 null 或 <= 0 时使用默认端口 22
|
||||||
|
* @param username 远程服务器用户名
|
||||||
|
* @param password 远程服务器密码 (如果使用密码认证)
|
||||||
|
* @param sshKeyPath SSH Key 文件路径 (如果使用密钥认证,password 参数可以为 null)
|
||||||
|
* @param localFilePath 本地要上传的文件路径
|
||||||
|
* @param remoteDir 远程服务器存放文件的目录 (例如: /home/user/uploads/)
|
||||||
|
* @param remoteFileName 上传到远程服务器的文件名 (为 null 或空字符串时使用本地文件名)
|
||||||
|
* @throws SftpUploadException 如果上传过程中发生任何错误
|
||||||
|
*/
|
||||||
|
public static void uploadFileViaSftp(
|
||||||
|
String remoteHost,
|
||||||
|
Integer remotePort,
|
||||||
|
String username,
|
||||||
|
String password,
|
||||||
|
String sshKeyPath,
|
||||||
|
String localFilePath,
|
||||||
|
String remoteDir,
|
||||||
|
String remoteFileName) throws SftpUploadException {
|
||||||
|
|
||||||
|
Session session = null;
|
||||||
|
Channel channel = null;
|
||||||
|
ChannelSftp channelSftp = null;
|
||||||
|
FileInputStream fis = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSch jsch = new JSch();
|
||||||
|
|
||||||
|
// 1. 添加身份认证信息 (密码或密钥)
|
||||||
|
if (sshKeyPath != null && !sshKeyPath.trim().isEmpty()) {
|
||||||
|
// 使用 SSH Key 认证
|
||||||
|
File sshKeyFile = new File(sshKeyPath);
|
||||||
|
if (!sshKeyFile.exists() || !sshKeyFile.isFile()) {
|
||||||
|
throw new SftpUploadException("SSH Key 文件不存在或不是一个有效文件: " + sshKeyPath);
|
||||||
|
}
|
||||||
|
jsch.addIdentity(sshKeyPath);
|
||||||
|
System.out.println("使用 SSH Key 认证: " + sshKeyPath);
|
||||||
|
} else if (password == null || password.trim().isEmpty()) {
|
||||||
|
// 如果没有提供密码或密钥路径,则认证信息不全
|
||||||
|
throw new SftpUploadException("必须提供密码或 SSH Key 路径进行 SFTP 认证.");
|
||||||
|
}
|
||||||
|
// 如果提供了密码,将在 getSession 后设置,因为 getSession 需要用户名、主机和端口先建立连接意图
|
||||||
|
|
||||||
|
|
||||||
|
// 2. 获取 Session
|
||||||
|
int port = (remotePort != null && remotePort > 0) ? remotePort : DEFAULT_SFTP_PORT;
|
||||||
|
session = jsch.getSession(username, remoteHost, port);
|
||||||
|
System.out.println("尝试连接 SFTP 服务器: " + username + "@" + remoteHost + ":" + port);
|
||||||
|
|
||||||
|
|
||||||
|
// 如果使用密码认证且提供了密码
|
||||||
|
if (password != null && !password.trim().isEmpty() && (sshKeyPath == null || sshKeyPath.trim().isEmpty())) {
|
||||||
|
session.setPassword(password);
|
||||||
|
System.out.println("使用密码认证.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置连接不进行主机密钥检查 (生产环境不推荐,应该配置 known_hosts)
|
||||||
|
// 在实际应用中,应该引导用户信任主机密钥或提前将主机密钥加入 known_hosts
|
||||||
|
java.util.Properties config = new java.util.Properties();
|
||||||
|
config.put("StrictHostKeyChecking", "no"); // !!! 生产环境请谨慎使用或配置正确的主机密钥检查 !!!
|
||||||
|
session.setConfig(config);
|
||||||
|
|
||||||
|
// 3. 连接 Session
|
||||||
|
session.connect();
|
||||||
|
System.out.println("SFTP Session 连接成功.");
|
||||||
|
|
||||||
|
// 4. 打开 SFTP Channel
|
||||||
|
channel = session.openChannel("sftp");
|
||||||
|
channel.connect();
|
||||||
|
System.out.println("SFTP Channel 打开成功.");
|
||||||
|
|
||||||
|
channelSftp = (ChannelSftp) channel;
|
||||||
|
|
||||||
|
// 5. 检查并切换到远程目录
|
||||||
|
try {
|
||||||
|
channelSftp.cd(remoteDir);
|
||||||
|
System.out.println("已切换到远程目录: " + remoteDir);
|
||||||
|
} catch (SftpException e) {
|
||||||
|
// 如果远程目录不存在,尝试创建
|
||||||
|
System.err.println("远程目录不存在: " + remoteDir + ",尝试创建...");
|
||||||
|
try {
|
||||||
|
// 尝试递归创建目录 (如果需要)
|
||||||
|
createRemoteDirRecursive(channelSftp, remoteDir);
|
||||||
|
channelSftp.cd(remoteDir); // 创建后再次切换
|
||||||
|
System.out.println("远程目录创建成功并已切换: " + remoteDir);
|
||||||
|
} catch (SftpException e2) {
|
||||||
|
// 创建目录失败
|
||||||
|
throw new SftpUploadException("远程目录创建失败: " + remoteDir, e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 获取本地文件流
|
||||||
|
File localFile = new File(localFilePath);
|
||||||
|
if (!localFile.exists() || !localFile.isFile()) {
|
||||||
|
throw new SftpUploadException("本地文件不存在或不是一个有效文件: " + localFilePath);
|
||||||
|
}
|
||||||
|
fis = new FileInputStream(localFile);
|
||||||
|
System.out.println("本地文件流获取成功: " + localFilePath);
|
||||||
|
|
||||||
|
// 7. 确定远程文件名
|
||||||
|
String finalRemoteFileName = (remoteFileName != null && !remoteFileName.trim().isEmpty()) ?
|
||||||
|
remoteFileName : localFile.getName();
|
||||||
|
System.out.println("最终上传到远程的文件名为: " + finalRemoteFileName);
|
||||||
|
|
||||||
|
// 8. 上传文件
|
||||||
|
channelSftp.put(fis, finalRemoteFileName);
|
||||||
|
System.out.println("文件上传成功!");
|
||||||
|
|
||||||
|
} catch (JSchException e) {
|
||||||
|
throw new SftpUploadException("SFTP 连接或认证失败: " + e.getMessage(), e);
|
||||||
|
} catch (SftpException e) {
|
||||||
|
throw new SftpUploadException("SFTP 操作失败 (如切换目录或上传文件): " + e.getMessage(), e);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
throw new SftpUploadException("本地文件未找到: " + e.getMessage(), e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new SftpUploadException("文件流读写异常: " + e.getMessage(), e);
|
||||||
|
} catch (SftpUploadException e) {
|
||||||
|
// 重新抛出自定义异常
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 捕获其他未知异常
|
||||||
|
throw new SftpUploadException("SFTP 上传过程中发生未知异常: " + e.getMessage(), e);
|
||||||
|
} finally {
|
||||||
|
// 9. 关闭资源 (确保在任何情况下都关闭)
|
||||||
|
if (fis != null) {
|
||||||
|
try {
|
||||||
|
fis.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("关闭本地文件流失败: " + e.getMessage());
|
||||||
|
e.printStackTrace(); // 打印堆栈以便调试
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (channelSftp != null) {
|
||||||
|
channelSftp.disconnect();
|
||||||
|
System.out.println("SFTP Channel 已断开.");
|
||||||
|
}
|
||||||
|
if (channel != null) {
|
||||||
|
channel.disconnect();
|
||||||
|
System.out.println("SFTP Channel 资源已释放.");
|
||||||
|
}
|
||||||
|
if (session != null) {
|
||||||
|
session.disconnect();
|
||||||
|
System.out.println("SFTP Session 已断开.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 辅助方法:递归创建远程目录
|
||||||
|
* @param channelSftp ChannelSftp 实例
|
||||||
|
* @param remoteDir 要创建的目录路径
|
||||||
|
* @throws SftpException 如果创建失败
|
||||||
|
*/
|
||||||
|
private static void createRemoteDirRecursive(ChannelSftp channelSftp, String remoteDir) throws SftpException {
|
||||||
|
// 标准化路径,去掉末尾的 /
|
||||||
|
String cleanRemoteDir = remoteDir.endsWith("/") ? remoteDir.substring(0, remoteDir.length() - 1) : remoteDir;
|
||||||
|
|
||||||
|
String[] pathElements = cleanRemoteDir.split("/");
|
||||||
|
StringBuilder currentDir = new StringBuilder();
|
||||||
|
try {
|
||||||
|
channelSftp.cd("/"); // 先回到根目录
|
||||||
|
} catch (SftpException e) {
|
||||||
|
// 理论上不应该失败,除非根目录都不可访问
|
||||||
|
throw new ServiceException();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String dir : pathElements) {
|
||||||
|
if (dir == null || dir.isEmpty()) {
|
||||||
|
continue; // 跳过空的路径元素,比如路径以/开头
|
||||||
|
}
|
||||||
|
currentDir.append("/").append(dir);
|
||||||
|
try {
|
||||||
|
channelSftp.cd(currentDir.toString());
|
||||||
|
// System.out.println("目录已存在: " + currentDir.toString());
|
||||||
|
} catch (SftpException e) {
|
||||||
|
if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
|
||||||
|
// 目录不存在,创建它
|
||||||
|
try {
|
||||||
|
System.out.println("创建目录: " + currentDir.toString());
|
||||||
|
channelSftp.mkdir(currentDir.toString());
|
||||||
|
channelSftp.cd(currentDir.toString()); // 创建后进入该目录
|
||||||
|
System.out.println("目录创建成功并进入: " + currentDir.toString());
|
||||||
|
} catch (SftpException e2) {
|
||||||
|
throw new SftpException(e2.id, "无法创建远程目录: " + currentDir.toString(), e2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 其他 SFTP 异常
|
||||||
|
throw new SftpException(e.id, "切换或检查远程目录失败: " + currentDir.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 自定义异常类,用于封装上传过程中的错误
|
||||||
|
public static class SftpUploadException extends Exception {
|
||||||
|
public SftpUploadException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
public SftpUploadException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 示例用法
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 使用密码认证示例
|
||||||
|
try {
|
||||||
|
System.out.println("--- 开始通过密码认证 SFTP 上传 ---");
|
||||||
|
SftpUploadUtil.uploadFileViaSftp(
|
||||||
|
"your-remote-server-ip", // 替换为你的远程服务器IP或主机名
|
||||||
|
22, // SFTP端口 (如果不是22,请修改)
|
||||||
|
"your-username", // 替换为你的 SFTP 用户名
|
||||||
|
"your-password", // 替换为你的 SFTP 密码
|
||||||
|
null, // SSH Key 路径 (密码认证时为 null)
|
||||||
|
"path/to/your/local/file.txt", // 替换为你要上传的本地文件路径
|
||||||
|
"/upload/target/directory/", // 替换为远程服务器上的目标目录 (确保用户有写入权限)
|
||||||
|
"uploaded_file.txt" // 上传到远程服务器的文件名 (null 或空则使用本地文件名)
|
||||||
|
);
|
||||||
|
System.out.println("文件通过密码认证 SFTP 上传成功!");
|
||||||
|
} catch (SftpUploadException e) {
|
||||||
|
System.err.println("文件通过密码认证 SFTP 上传失败: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("\n------------------------------------\n");
|
||||||
|
|
||||||
|
// 使用 SSH Key 认证示例 (假设你有一个私钥文件)
|
||||||
|
// 请确保你的 SSH Key 不需要密码 (passphrase)
|
||||||
|
try {
|
||||||
|
System.out.println("--- 开始通过 SSH Key 认证 SFTP 上传 ---");
|
||||||
|
SftpUploadUtil.uploadFileViaSftp(
|
||||||
|
"your-remote-server-ip", // 替换为你的远程服务器IP或主机名
|
||||||
|
null, // SFTP端口 (使用默认端口 22)
|
||||||
|
"your-username", // 替换为你的 SFTP 用户名
|
||||||
|
null, // 密码 (SSH Key 认证时为 null)
|
||||||
|
"/path/to/your/private/key",// 替换为你的本地私钥文件路径 (例如: /home/user/.ssh/id_rsa)
|
||||||
|
"path/to/another/local/file.log", // 替换为另一个你要上传的本地文件路径
|
||||||
|
"/upload/other/files/", // 替换为另一个远程目录
|
||||||
|
null // 远程文件名使用本地文件名
|
||||||
|
);
|
||||||
|
System.out.println("文件通过 SSH Key 认证 SFTP 上传成功!");
|
||||||
|
} catch (SftpUploadException e) {
|
||||||
|
System.err.println("文件通过 SSH Key 认证 SFTP 上传失败: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
System.out.println("\n------------------------------------\n");
|
||||||
|
}
|
||||||
|
}
|
30
ops-server/src/test/java/cd/casic/server/SftpTest.java
Normal file
30
ops-server/src/test/java/cd/casic/server/SftpTest.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package cd.casic.server;
|
||||||
|
|
||||||
|
import cd.casic.ci.process.process.dataObject.machine.MachineInfo;
|
||||||
|
import cd.casic.ci.process.process.service.machine.MachineInfoService;
|
||||||
|
import cd.casic.ci.process.util.CryptogramUtil;
|
||||||
|
import cd.casic.ci.process.util.SftpUploadUtil;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestClassOrder;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
@SpringBootTest(classes = {OpsServerApplication.class})
|
||||||
|
@ActiveProfiles("local")
|
||||||
|
public class SftpTest {
|
||||||
|
@Resource
|
||||||
|
MachineInfoService machineInfoService;
|
||||||
|
@Test
|
||||||
|
public void test01() throws SftpUploadUtil.SftpUploadException {
|
||||||
|
MachineInfo byId = machineInfoService.getById("1");
|
||||||
|
File file = new File("src/test/java/cd/casic/server/text.txt");
|
||||||
|
System.out.println(file.getAbsolutePath());
|
||||||
|
System.out.println(file.exists());
|
||||||
|
System.out.println(file.getName());
|
||||||
|
SftpUploadUtil.uploadFileViaSftp(byId.getMachineHost(),byId.getSshPort(),byId.getUsername(), CryptogramUtil.doDecrypt(byId.getPassword()),null,file.getAbsolutePath(),"/home/casic/706/ai_test_527","到此一游.txt");
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user