feat:(unify): 完成代码的拉去和编辑

This commit is contained in:
opensnail 2024-12-14 12:51:40 +08:00
parent d1bc4af726
commit cbee3797fe
16 changed files with 537 additions and 16 deletions

View File

@ -31,6 +31,7 @@
<java-jwt.version>4.4.0</java-jwt.version> <java-jwt.version>4.4.0</java-jwt.version>
<perf4j.version>0.9.16</perf4j.version> <perf4j.version>0.9.16</perf4j.version>
<guava.version>33.2.0-jre</guava.version> <guava.version>33.2.0-jre</guava.version>
<docker-java.version>3.4.0</docker-java.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@ -120,6 +121,17 @@
<artifactId>perf4j</artifactId> <artifactId>perf4j</artifactId>
<version>${perf4j.version}</version> <version>${perf4j.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
<version>${docker-java.version}</version>
</dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java-transport-okhttp</artifactId>
<version>${docker-java.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

@ -95,6 +95,15 @@
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId> <artifactId>hutool-crypto</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
</dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java-transport-okhttp</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -0,0 +1,44 @@
package com.aizuda.snailjob.server.web.controller;
import com.aizuda.snailjob.server.web.model.response.ContainerVO;
import com.aizuda.snailjob.server.web.service.DockerService;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.model.Frame;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/docker")
@RequiredArgsConstructor
public class DockerController {
private final DockerService dockerService;
@GetMapping("/container/list")
public List<ContainerVO> getContainerList(@RequestParam("containerName") String containerName) {
return dockerService.getContainerList(containerName);
}
@GetMapping("/container/{id}")
public InspectContainerResponse getContainerById(@PathVariable("id") String id) {
return dockerService.getContainerById(id);
}
@GetMapping("/container/log/{id}")
public ResultCallback<Frame> getContainerLogById(@PathVariable("id") String id) throws InterruptedException {
return dockerService.getContainerLogById(id);
}
@GetMapping("/create/container")
public boolean createContainer(@RequestParam("containerName") String containerName, @RequestParam("imageName") String imageName) {
return dockerService.createContainer(containerName, imageName);
}
@GetMapping("/build/image")
public boolean buildImage(@RequestParam("imagerName") String imagerName, @RequestParam("path") String path) {
return dockerService.buildImage(imagerName, path);
}
}

View File

@ -1,41 +1,70 @@
package com.aizuda.snailjob.server.web.controller; package com.aizuda.snailjob.server.web.controller;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.aizuda.snailjob.server.common.exception.SnailJobServerException; import com.aizuda.snailjob.server.common.exception.SnailJobServerException;
import com.aizuda.snailjob.server.web.model.enums.FileTypeEnum;
import com.aizuda.snailjob.server.web.model.response.FileVO;
import com.aizuda.snailjob.server.web.model.response.SaveFileRequestVO; import com.aizuda.snailjob.server.web.model.response.SaveFileRequestVO;
import com.aizuda.snailjob.server.web.model.response.ViewFileResponseVO; import com.aizuda.snailjob.server.web.model.response.ViewFileResponseVO;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@RestController @RestController
@RequestMapping("/file") @RequestMapping("/file")
@RequiredArgsConstructor @RequiredArgsConstructor
public class FileViewerController { public class FileViewerController {
// todo 用户配置或者默认是运行jar的所在目录
private static final String workdir = "/Users/zhangshuguang/";
// 显示文件列表 // 显示文件列表
@GetMapping("/files") @GetMapping("/files")
public List<String> listFiles(@RequestParam("rootPath") String rootPath) { public List<FileVO> listFiles(@RequestParam(value = "directory") String directory) {
try { try {
// 读取目录下的文件列表 // 读取目录下的文件列表
return Files.list(Paths.get(rootPath)) return Files.list(Paths.get(workdir + StrUtil.SLASH + directory))
.map(path -> path.getFileName().toString()) .map(path ->{
.toList(); File file = path.toFile();
FileVO fileVO = new FileVO();
fileVO.setFileName(file.getName());
// fileVO.setFilePath(file.getPath());
if (file.isDirectory()) {
fileVO.setFileType(FileTypeEnum.DIRECTORY.getType());
} else {
fileVO.setFileType(FileTypeEnum.FILE.getType());
}
return fileVO;
})
.toList().stream().sorted(new Comparator<FileVO>() {
@Override
public int compare(FileVO o1, FileVO o2) {
if (o2.getFileType().compareTo(o1.getFileType()) == 0) {
return o1.getFileName().compareTo(o2.getFileName());
}
return o2.getFileType().compareTo(o1.getFileType());
}
}).collect(Collectors.toList());
} catch (IOException e) { } catch (IOException e) {
throw new SnailJobServerException("获取文件列表失败", e); throw new SnailJobServerException("获取文件列表失败", e);
} }
} }
// 显示文件内容 // 显示文件内容
@GetMapping("/file") @GetMapping("/detail")
public ViewFileResponseVO viewFile(@RequestParam("fileName") String fileName, @RequestParam("fullPath") String fullPath) { public ViewFileResponseVO viewFile(@RequestParam(value = "directory") String directory) {
try { try {
// 构建文件路径 // 构建文件路径
String filePath = Paths.get(fullPath, fileName).toString(); String filePath = Paths.get(workdir + StrUtil.SLASH + directory).toString();
// 读取文件内容 // 读取文件内容
String s = Files.readString(Paths.get(filePath)); String s = Files.readString(Paths.get(filePath));
@ -48,14 +77,9 @@ public class FileViewerController {
} }
// 保存修改后的文件内容 // 保存修改后的文件内容
@PostMapping("/file") @PutMapping
public Boolean saveFile(@RequestBody SaveFileRequestVO requestVO) { public Boolean saveFile(@RequestBody SaveFileRequestVO requestVO) {
try { FileUtil.writeUtf8String(requestVO.getContent().trim(), workdir + StrUtil.SLASH + requestVO.getFileName());
String filePath = Paths.get(requestVO.getFilePath(), requestVO.getFileName()).toString();
Files.writeString(Paths.get(filePath), requestVO.getContent()); // 更新文件内容
return true; return true;
} catch (IOException e) {
throw new SnailJobServerException("文件更新时候", e);
}
} }
} }

View File

@ -0,0 +1,17 @@
package com.aizuda.snailjob.server.web.controller;
import com.aizuda.snailjob.server.web.model.request.RunPythonRequestVO;
import com.aizuda.snailjob.server.web.service.PythonService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/python")
@RequiredArgsConstructor
public class PythonController {
private final PythonService pythonService;
@PostMapping("/run")
public Boolean run(@RequestBody RunPythonRequestVO requestVO) {
return pythonService.runPython(requestVO);
}
}

View File

@ -0,0 +1,72 @@
package com.aizuda.snailjob.server.web.model.base;
import com.github.dockerjava.api.async.ResultCallbackTemplate;
import com.github.dockerjava.api.exception.DockerClientException;
import com.github.dockerjava.api.model.BuildResponseItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;
public class SjBuildImageResultCallback extends ResultCallbackTemplate<com.github.dockerjava.api.command.BuildImageResultCallback, BuildResponseItem> {
private static final Logger LOGGER = LoggerFactory.getLogger(com.github.dockerjava.api.command.BuildImageResultCallback.class);
private String imageId;
private String error;
@Override
public void onNext(BuildResponseItem item) {
if (item.isBuildSuccessIndicated()) {
this.imageId = item.getImageId();
} else if (item.isErrorIndicated()) {
this.error = item.getError();
}
LOGGER.info("{}", item.getStream());
}
/**
* Awaits the image id from the response stream.
*
* @throws DockerClientException
* if the build fails.
*/
public String awaitImageId() {
try {
awaitCompletion();
} catch (InterruptedException e) {
throw new DockerClientException("", e);
}
return getImageId();
}
/**
* Awaits the image id from the response stream.
*
* @throws DockerClientException
* if the build fails or the timeout occurs.
*/
public String awaitImageId(long timeout, TimeUnit timeUnit) {
try {
awaitCompletion(timeout, timeUnit);
} catch (InterruptedException e) {
throw new DockerClientException("Awaiting image id interrupted: ", e);
}
return getImageId();
}
private String getImageId() {
if (error != null) {
throw new DockerClientException("Could not build image: " + error);
}
if (imageId != null) {
return imageId;
}
throw new DockerClientException("Could not build image");
}
}

View File

@ -0,0 +1,14 @@
package com.aizuda.snailjob.server.web.model.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum FileTypeEnum {
FILE(1),
DIRECTORY(2);
private final int type;
}

View File

@ -0,0 +1,11 @@
package com.aizuda.snailjob.server.web.model.request;
import lombok.Data;
@Data
public class RunPythonRequestVO {
private String pythonPath;
private String command;
}

View File

@ -0,0 +1,31 @@
package com.aizuda.snailjob.server.web.model.response;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
public class ContainerVO {
private String id;
private String name;
private String image;
private String status;
private String state;
private List<ContainerPortVO> ports;
@Data
public static class ContainerPortVO {
private String ip;
private Integer privatePort;
private Integer publicPort;
private String type;
}
}

View File

@ -0,0 +1,14 @@
package com.aizuda.snailjob.server.web.model.response;
import com.aizuda.snailjob.server.web.model.enums.FileTypeEnum;
import lombok.Data;
@Data
public class FileVO {
private String fileName;
private String filePath;
private Integer fileType;
}

View File

@ -1,10 +1,8 @@
package com.aizuda.snailjob.server.web.model.response; package com.aizuda.snailjob.server.web.model.response;
import lombok.Builder;
import lombok.Data; import lombok.Data;
@Data @Data
@Builder
public class SaveFileRequestVO { public class SaveFileRequestVO {
private String fileName; private String fileName;

View File

@ -0,0 +1,21 @@
package com.aizuda.snailjob.server.web.service;
import com.aizuda.snailjob.server.web.model.response.ContainerVO;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.model.Frame;
import java.util.List;
public interface DockerService {
boolean buildImage(String groupName, String path);
boolean createContainer(String containerName, String imageName);
List<ContainerVO> getContainerList(String containerName);
InspectContainerResponse getContainerById(String id);
ResultCallback<Frame> getContainerLogById(String id) throws InterruptedException;
}

View File

@ -0,0 +1,9 @@
package com.aizuda.snailjob.server.web.service;
import com.aizuda.snailjob.server.web.model.request.RunPythonRequestVO;
public interface PythonService {
boolean runPython(RunPythonRequestVO pythonPath);
}

View File

@ -0,0 +1,37 @@
package com.aizuda.snailjob.server.web.service.handler;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.okhttp.OkDockerHttpClient;
import org.springframework.stereotype.Component;
@Component
public class DockerHandler {
public DockerClient getDockerClient() {
DefaultDockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder()
.withDockerTlsVerify(false)
// 这里填最上面填的ip端口号ip换成服务器ip
.withDockerHost("unix:///var/run/docker.sock")
// docker API版本号可以用docker version查看
// .withApiVersion("1.41")
// 默认
// .withRegistryUrl("https://index.docker.io/v1/")
.build();
OkDockerHttpClient build = new OkDockerHttpClient.Builder()
.dockerHost(config.getDockerHost())
.sslConfig(config.getSSLConfig())
.connectTimeout(30 * 100)
.build();
// Create Docker client
return DockerClientBuilder
.getInstance(config)
.withDockerHttpClient(build)
.build();
}
}

View File

@ -0,0 +1,131 @@
package com.aizuda.snailjob.server.web.service.impl;
import com.aizuda.snailjob.common.core.util.StreamUtils;
import com.aizuda.snailjob.server.common.util.DateUtils;
import com.aizuda.snailjob.server.web.model.base.SjBuildImageResultCallback;
import com.aizuda.snailjob.server.web.model.response.ContainerVO;
import com.aizuda.snailjob.server.web.service.DockerService;
import com.aizuda.snailjob.server.web.service.handler.DockerHandler;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.async.ResultCallbackTemplate;
import com.github.dockerjava.api.command.*;
import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.ContainerPort;
import com.github.dockerjava.api.model.Frame;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import static com.aizuda.snailjob.server.common.util.DateUtils.PURE_DATETIME_MS_PATTERN;
@Service
@RequiredArgsConstructor
public class DockerServiceImpl implements DockerService {
private final DockerHandler dockerHandler;
@Override
public boolean buildImage(String groupName, String path) {
String format = String.format("sj_%s_%s", groupName, DateUtils.toNowFormat(PURE_DATETIME_MS_PATTERN));
DockerClient dockerClient = dockerHandler.getDockerClient();
BuildImageCmd imageCmd = dockerClient
.buildImageCmd(new File(path))
.withTags(Sets.newHashSet(format));
imageCmd.exec(new SjBuildImageResultCallback()).awaitImageId();
return false;
}
@Override
public boolean createContainer(String containerName, String imageName) {
DockerClient dockerClient = dockerHandler.getDockerClient();
String image = String.format("%s:latest", imageName);
containerName = String.format("sj_%s_%s", containerName, DateUtils.toNowFormat(PURE_DATETIME_MS_PATTERN));
// Pull an image
// try {
// dockerClient.pullImageCmd(String.format("%s:latest", imageName)).start().awaitCompletion();
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
Map<String, String> labels = Maps.newHashMap();
labels.put("name", "snail_job");
// Create a container
CreateContainerResponse container = dockerClient
.createContainerCmd(image)
.withName(containerName)
.withLabels(labels)
// .withCmd("echo", "Hello, Docker-Java!")
.exec();
// Start the container
dockerClient.startContainerCmd(container.getId()).exec();
return false;
}
@Override
public List<ContainerVO> getContainerList(String containerName) {
DockerClient dockerClient = dockerHandler.getDockerClient();
Map<String, String> labels = Maps.newHashMap();
labels.put("app-name", "snail-job-server");
ListContainersCmd listContainersCmd = dockerClient.listContainersCmd();
List<Container> containerList = listContainersCmd.withLabelFilter(labels).withShowAll(true).exec();
return StreamUtils.toList(containerList, container -> {
ContainerVO containerVO = new ContainerVO();
containerVO.setId(container.getId());
containerVO.setImage(container.getImage());
containerVO.setName(container.getNames()[0]);
containerVO.setStatus(container.getStatus());
containerVO.setState(container.getState());
List<ContainerVO.ContainerPortVO> portVOS = StreamUtils.toList(Arrays.stream(container.getPorts()).toList(), new Function<ContainerPort, ContainerVO.ContainerPortVO>() {
@Override
public ContainerVO.ContainerPortVO apply(ContainerPort containerPort) {
ContainerVO.ContainerPortVO portVO = new ContainerVO.ContainerPortVO();
portVO.setPublicPort(containerPort.getPublicPort());
portVO.setPrivatePort(containerPort.getPrivatePort());
portVO.setIp(containerPort.getIp());
portVO.setType(containerPort.getType());
return portVO;
}
});
containerVO.setPorts(portVOS);
return containerVO;
});
}
@Override
public InspectContainerResponse getContainerById(String id) {
DockerClient dockerClient = dockerHandler.getDockerClient();
return dockerClient.inspectContainerCmd(id).exec();
}
@Override
public ResultCallback<Frame> getContainerLogById(String id) throws InterruptedException {
DockerClient dockerClient = dockerHandler.getDockerClient();
return dockerClient.logContainerCmd(id)
.withStdErr(true)
.withStdOut(true)
.withFollowStream(true)
.withTailAll()
.exec(new ResultCallbackTemplate<>() {
@Override
public void onNext(Frame object) {
// ws处理
System.out.println(object);
}
}).awaitStarted();
}
}

View File

@ -0,0 +1,77 @@
package com.aizuda.snailjob.server.web.service.impl;
import cn.hutool.core.util.StrUtil;
import com.aizuda.snailjob.server.web.model.request.RunPythonRequestVO;
import com.aizuda.snailjob.server.web.service.PythonService;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@Service
public class PythonServiceImpl implements PythonService {
private static final String workdir = "/Users/zhangshuguang/";
@Override
public boolean runPython(RunPythonRequestVO requestVO) {
return execPython(requestVO);
}
public boolean execPython(RunPythonRequestVO requestVO) {
try {
String command = requestVO.getCommand();
String[] split = command.split("&&");
// Python 脚本路径
String pythonScriptPath = workdir + StrUtil.SLASH + requestVO.getPythonPath();
String pythonScriptTxt = workdir + StrUtil.SLASH + StrUtil.replaceFirst(requestVO.getPythonPath(), "main.py", "requirements.txt");
// 构建命令
ProcessBuilder pipBuilder = new ProcessBuilder(split[0], pythonScriptTxt);
pipBuilder.redirectErrorStream(true);
// 启动进程
Process pipProcess = pipBuilder.start();
printProcessOutput(pipProcess);
// 等待脚本执行结束
int pipExitCode = pipProcess.waitFor();
if (pipExitCode != 0) {
System.out.println("Script exited with code: " + pipExitCode);
return false;
}
// 构建命令
ProcessBuilder processBuilder = new ProcessBuilder(split[1], pythonScriptPath);
processBuilder.redirectErrorStream(true);
// 启动进程
Process process = processBuilder.start();
printProcessOutput(process);
// 等待脚本执行结束
int exitCode = process.waitFor();
if (exitCode != 0) {
System.out.println("Script exited with code: " + exitCode);
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
private static void printProcessOutput(Process process) throws IOException {
// 获取脚本输出
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}