package com.vci.ubcs.deploy.service.impl;
|
|
|
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSONArray;
|
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.nacos.shaded.com.google.protobuf.ServiceException;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
|
import com.vci.ubcs.deploy.entity.DeployApps;
|
import com.vci.ubcs.deploy.enumpack.CmdConfigEnum;
|
import com.vci.ubcs.deploy.mapper.DeployAppsMapper;
|
import com.vci.ubcs.deploy.service.IDeployAppsService;
|
import com.vci.ubcs.deploy.vo.DeployAppsVO;
|
import com.vci.ubcs.starter.util.HttpUtils;
|
import lombok.RequiredArgsConstructor;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.utils.Func;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.cloud.client.ServiceInstance;
|
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
import org.springframework.context.EnvironmentAware;
|
import org.springframework.core.env.Environment;
|
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.MediaType;
|
import org.springframework.stereotype.Service;
|
import org.springframework.web.multipart.MultipartFile;
|
|
import javax.servlet.ServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
import java.io.*;
|
import java.net.URL;
|
import java.nio.file.Files;
|
import java.nio.file.Path;
|
import java.nio.file.Paths;
|
import java.nio.file.StandardCopyOption;
|
import java.time.LocalDateTime;
|
import java.time.format.DateTimeFormatter;
|
import java.util.*;
|
import java.util.stream.Collectors;
|
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipFile;
|
|
/**
|
* @author ludc
|
* @date 2024/1/7 19:54
|
*/
|
@Service
|
@RequiredArgsConstructor
|
@Slf4j
|
public class DeployAppsServiceImpl extends ServiceImpl<DeployAppsMapper, DeployApps> implements IDeployAppsService, EnvironmentAware {
|
|
private final DeployAppsMapper deployAppsMapper;
|
|
@Value("${password-free.pwd-free-addr:localhost}")
|
private String pwdFreeAddr;
|
|
/**
|
* 通过服务注册中心获取网关的端口号
|
*/
|
@Autowired
|
private DiscoveryClient discoveryClient;
|
|
/**
|
* 各个服务存放的的父路径
|
*/
|
@Value("${local-log.parent-path:/data1/ubcs/ubcs-server}")
|
private String PARENTPATH;
|
|
/**
|
* 日志文件的具体位置
|
*/
|
@Value("${local-log.log-path:/target/log}")
|
private String LOGPATH;
|
|
/**
|
* 根据操作系统生成分隔符
|
*/
|
private String SEPARATOR = "/";
|
|
/**
|
* 根据当前运行的环境,对配置的日志路径格式进行调整
|
* @param environment
|
*/
|
@Override
|
public void setEnvironment(Environment environment) {
|
String os = environment.getProperty("os.name").toLowerCase();
|
if (!os.contains("win")) {
|
this.SEPARATOR = "/";
|
}else{
|
this.SEPARATOR = "\\";
|
}
|
}
|
|
@Override
|
public List<DeployAppsVO> getApplications(ServletRequest servletRequest) throws ServiceException {
|
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
String baldeAuth = request.getHeaders("Blade-Auth").nextElement();
|
String cookie = request.getHeaders("Cookie").nextElement();
|
// 免密登录接口地址
|
String loginUrl = "http://"+pwdFreeAddr+":"+this.getGatewayPort("ubcs-gateway")+"/ubcs-admin/applications";
|
log.debug("获取服务信息调用地址:"+loginUrl);
|
// 请求ubcs-admin获取服务信息
|
HttpHeaders headers = new HttpHeaders();
|
ArrayList<MediaType> mediaTypes = new ArrayList<>();
|
mediaTypes.add(MediaType.APPLICATION_JSON);
|
headers.set("Authorization","Basic c2FiZXI6c2FiZXJfc2VjcmV0");
|
headers.set("Blade-Auth",baldeAuth);
|
headers.set("Cookie",cookie);
|
headers.setAccept(mediaTypes);
|
List<DeployAppsVO> deployAppsVOList = new ArrayList<>();
|
try {
|
// 发送GET请求
|
String responseBody = HttpUtils.get(loginUrl, headers);
|
if(Func.isNotEmpty(responseBody)){
|
// 将 JSON 字符串转换为 JSONArray
|
JSONArray jsonArray = JSON.parseArray(responseBody);
|
for (Object obj : jsonArray) {
|
DeployAppsVO deployAppsVO = new DeployAppsVO();
|
JSONObject jsonObject = (JSONObject) obj;
|
List instances = ((List) jsonObject.get("instances"));
|
deployAppsVO.setName(jsonObject.get("name").toString());
|
String serviceUrl = ((JSONObject) ((JSONObject) instances.get(0)).get("registration")).get("serviceUrl").toString();
|
deployAppsVO.setPort(String.valueOf(new URL(serviceUrl).getPort()));
|
deployAppsVO.setServiceNum(instances.size());
|
deployAppsVO.setStatus(jsonObject.get("status").toString());
|
deployAppsVO.setStatusTimestamp(jsonObject.get("statusTimestamp").toString());
|
deployAppsVOList.add(deployAppsVO);
|
}
|
}
|
}catch (Exception e){
|
throw new ServiceException("调用ubcs-admin获取服务信息失败,原因:"+e.getMessage());
|
}
|
// 再查询库中已经存在的服务配置信息,进行是否已经启动的判断
|
List<DeployApps> deployApps = deployAppsMapper.selectList(null);
|
// 库中未配置,直接返回正在运行的服务信息
|
if(deployApps.isEmpty()){
|
return deployAppsVOList;
|
}
|
// 筛选出不在运行的并生成默认的服务信息
|
List<DeployAppsVO> deployAppsVOS1 = deployApps.stream()
|
.filter(deployApp -> deployAppsVOList.stream()
|
.noneMatch(deployAppVO -> deployApp.getServerName().equals(deployAppVO.getName()) &&
|
deployApp.getServerName().equals(deployAppVO.getName())))
|
.map(deployApp -> {
|
DeployAppsVO deployAppsVO2 = new DeployAppsVO(deployApp.getServerName(), "DOWN", "", "", 0);
|
if(deployApp.getServerName().equals("web")){
|
deployAppsVO2.setStatus("UP");
|
deployAppsVO2.setPort("8080");
|
deployAppsVO2.setServiceNum(1);
|
}
|
return deployAppsVO2;
|
})
|
.collect(Collectors.toList());
|
deployAppsVOList.addAll(deployAppsVOS1);
|
return deployAppsVOList;
|
}
|
|
@Override
|
public DeployApps saveOrGetServiceConfInfo(DeployAppsVO deployAppsVO) throws ServiceException {
|
if(deployAppsVO.getName().isEmpty()){
|
throw new ServiceException("缺少必传参数name");
|
}
|
List<DeployApps> deployApps = deployAppsMapper.selectList(Wrappers.<DeployApps>query().lambda().eq(DeployApps::getServerName, deployAppsVO.getName()));
|
if(Func.isNotEmpty(deployApps)){
|
return deployApps.get(0);
|
}
|
// 未从库中查询到,需要生成服务信息保存默认信息到库中
|
DeployApps defaultDeployApps = new DeployApps();
|
defaultDeployApps.setLogPath(PARENTPATH + SEPARATOR + deployAppsVO.getName().replace("-","_") + LOGPATH);
|
defaultDeployApps.setServerName(deployAppsVO.getName());
|
defaultDeployApps.setStartCmd(CmdConfigEnum.START_CMD.getValue() + deployAppsVO.getName());
|
defaultDeployApps.setStopCmd(CmdConfigEnum.STOP_CMD.getValue() + deployAppsVO.getName());
|
defaultDeployApps.setRestartCmd(CmdConfigEnum.RESTART_CMD.getValue() + deployAppsVO.getName());
|
defaultDeployApps.setServerPath(PARENTPATH + SEPARATOR + deployAppsVO.getName().replace("-","_"));
|
int eft = deployAppsMapper.insert(defaultDeployApps);
|
if (!SqlHelper.retBool(eft)) {
|
throw new ServiceException("生成默认服务信息到库中时失败!");
|
}
|
return defaultDeployApps;
|
}
|
|
/**
|
* 修改或保存
|
* @param deployAppsVO
|
* @return
|
* @throws ServiceException
|
*/
|
@Override
|
public R saveOrUpdateServiceInfo(DeployAppsVO deployAppsVO) throws ServiceException {
|
boolean checkBoolean = checkCmdVer(deployAppsVO.getStartCmd()) && checkCmdVer(deployAppsVO.getStopCmd()) && checkCmdVer(deployAppsVO.getRestartCmd());
|
if(checkBoolean){
|
return R.fail("配置的命令中包含危险命令,如:rm、mv、rm -rf、chmod等命令和关键字! ");
|
}
|
return R.status(this.saveOrUpdate(deployAppsVO));
|
}
|
|
/**
|
* 新增服务信息
|
* @param deployApps
|
* @return
|
* @throws ServiceException
|
*/
|
@Override
|
public boolean addSave(DeployApps deployApps) throws ServiceException {
|
if (Func.isBlank(deployApps.getServerName()) || Func.isBlank(deployApps.getServerPath())) {
|
throw new ServiceException("必传参数[服务名称,服务存放路径]不能为空");
|
}
|
List<DeployApps> deployAppsList = deployAppsMapper.selectList(Wrappers.<DeployApps>query().lambda().eq(DeployApps::getServerName, deployApps.getServerName()));
|
if (!deployAppsList.isEmpty()) {
|
throw new ServiceException("新增服务的服务名重复!");
|
}
|
return SqlHelper.retBool(deployAppsMapper.insert(deployApps));
|
}
|
|
/**
|
* 更新文件上传
|
* @param files
|
* @param serverName
|
* @return
|
*/
|
@Override
|
public R importJarUpdate(MultipartFile[] files, String serverName) throws ServiceException {
|
// 根据服务名查看到服务相关信息
|
List<DeployApps> deployAppsDB = deployAppsMapper.selectList(Wrappers.<DeployApps>query().lambda().eq(DeployApps::getServerName, serverName));
|
if(deployAppsDB.isEmpty()){
|
return R.fail("No configuration information related to "+ serverName +" service found");
|
}
|
// 遍历MultipartFile数组,逐个处理文件
|
try {
|
for (MultipartFile file : files) {
|
// 配置了备份文件路径,先备份再替换
|
if(Func.isNotEmpty(deployAppsDB.get(0).getFileBack())){
|
File backFile = new File(deployAppsDB.get(0).getFileBack());
|
// 路径不存在就创建
|
if (!backFile.exists()) {
|
backFile.mkdirs();
|
}
|
String backName = "";
|
String fileType = "file";
|
// 是压缩文件,因为只会存在两种情况,文件名是压缩文件,或者文件(.jar类型的文件)
|
// 当前时间
|
String currentDateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
if (file.getContentType().equals("application/zip") || file.getContentType().equals("application/x-zip-compressed")) {
|
backName = file.getOriginalFilename().replace(".zip","_" + currentDateTime);
|
}else{
|
backName = file.getOriginalFilename().replace(".","_" + currentDateTime + ".");
|
}
|
File source = new File(deployAppsDB.get(0).getServerPath() + this.SEPARATOR + file.getOriginalFilename().replace(".zip", ""));
|
File destination = new File(deployAppsDB.get(0).getFileBack() + this.SEPARATOR + backName);
|
copyFolder(source, destination);
|
}
|
Path filePath = Paths.get(deployAppsDB.get(0).getServerPath(), file.getOriginalFilename());
|
Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
|
// 检查文件类型,如果是压缩文件则解压缩
|
if (file.getContentType().equals("application/zip") || file.getContentType().equals("application/x-zip-compressed")) {
|
//sourcePath压缩包文件路径
|
try (ZipFile zipFile = new ZipFile(new File(deployAppsDB.get(0).getServerPath()+ this.SEPARATOR +file.getOriginalFilename()))) {
|
Enumeration enumeration = zipFile.entries();
|
while (enumeration.hasMoreElements()) {
|
//依次获取压缩包内的文件实体对象
|
ZipEntry entry = (ZipEntry) enumeration.nextElement();
|
String name = entry.getName();
|
if (entry.isDirectory()) {
|
continue;
|
}
|
try (BufferedInputStream inputStream = new BufferedInputStream(zipFile.getInputStream(entry))) {
|
// 需要判断文件所在的目录是否存在,处理压缩包里面有文件夹的情况
|
String outName = deployAppsDB.get(0).getServerPath() + this.SEPARATOR + name;
|
File outFile = new File(outName);
|
File tempFile = new File(outName.substring(0, outName.lastIndexOf("/")));
|
if (!tempFile.exists()) {
|
tempFile.mkdirs();
|
}
|
try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outFile))) {
|
int len;
|
byte[] buffer = new byte[1024];
|
while ((len = inputStream.read(buffer)) > 0) {
|
outputStream.write(buffer, 0, len);
|
}
|
}
|
}
|
}
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
File file1 = new File(deployAppsDB.get(0).getServerPath() + SEPARATOR + file.getOriginalFilename());
|
// 压缩文件上传成功之后,删除解压文件
|
file1.delete();
|
}
|
}
|
String output = execute(deployAppsDB.get(0),"UP");
|
return R.success(output.toString());
|
} catch (IOException e) {
|
e.printStackTrace();
|
log.error(e.getMessage());
|
return R.fail("Failed to upload files");
|
}
|
|
}
|
|
/**
|
* 执行命令
|
* @param deployAppsVO
|
* @return
|
*/
|
@Override
|
public R cmdExecute(DeployAppsVO deployAppsVO) throws ServiceException {
|
String excuteRes = "";
|
try {
|
List<DeployApps> deployAppsDB = deployAppsMapper.selectList(Wrappers.<DeployApps>query().lambda().eq(DeployApps::getServerName, deployAppsVO.getName()));
|
if(deployAppsDB.isEmpty()){
|
return R.fail("命令执行出错,库中未找到"+ deployAppsVO.getName() +"服务相关配置:" );
|
}
|
excuteRes = execute(deployAppsDB.get(0),deployAppsVO.getStatus());
|
return R.success("命令执行结束:"+excuteRes);
|
}catch (Exception e){
|
throw new ServiceException(e.getMessage());
|
}
|
}
|
|
/**
|
* 执行命令
|
* @param deployApps
|
* @return
|
* @throws ServiceException
|
*/
|
public String execute(DeployApps deployApps,String type) throws ServiceException {
|
// 处理上传文件的逻辑
|
StringBuilder output = new StringBuilder();
|
try {
|
String cmd = "";
|
if(type.equalsIgnoreCase("UP")){
|
cmd = deployApps.getRestartCmd();
|
}else {
|
cmd = deployApps.getStartCmd();
|
}
|
if(Func.isEmpty(cmd)){
|
return "The executed command is empty";
|
}
|
if(checkCmdVer(cmd)){
|
return "配置的命令中包含危险命令,如:rm、mv、rm -rf、chmod等命令和关键词! ";
|
}
|
// 执行Linux命令
|
log.info("开始执行命令:"+cmd);
|
// Process process = Runtime.getRuntime().exec(cmd);
|
ProcessBuilder processBuilder = new ProcessBuilder(cmd.split("\\s"));
|
// processBuilder.command(cmd);
|
Process process = processBuilder.start();
|
// 等待命令执行完成
|
int exitCode = process.waitFor();
|
|
InputStream inputStream = process.getInputStream();
|
OutputStream outputStream = process.getOutputStream();
|
InputStream errorStream = process.getErrorStream();
|
// 读取命令执行结果
|
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
|
String line;
|
while ((line = reader.readLine()) != null) {
|
output.append(line).append("\n");
|
}
|
reader.close();
|
|
// 读取命令执行的错误输出流
|
BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream));
|
StringBuilder errorOutput = new StringBuilder();
|
String errorLine;
|
while ((errorLine = errorReader.readLine()) != null) {
|
errorOutput.append(errorLine).append("\n");
|
}
|
String errorOutputString = errorOutput.toString();
|
errorReader.close();
|
|
log.info("错误输出:" + errorOutputString);
|
int exitValue = process.exitValue();
|
log.info("命令执行结果:" + output.toString()+":"+exitCode+","+exitValue);
|
return output.toString();
|
}catch (IOException | InterruptedException e){
|
e.printStackTrace();
|
log.error("命令执行出错,原因:" + e.getMessage());
|
throw new ServiceException("Command execution failed"+e.getMessage());
|
}
|
}
|
|
/**
|
* 文件备份操作
|
* @param source 源文件
|
* @param destination 文件备份路径
|
* @throws IOException
|
*/
|
private void copyFolder(File source, File destination) throws IOException {
|
// 文件存在才需要备份
|
if(source.exists()){
|
if (source.isDirectory()) {
|
if (!destination.exists()) {
|
destination.mkdir();
|
}
|
String[] files = source.list();
|
for (String file : files) {
|
File srcFile = new File(source, file);
|
File destFile = new File(destination, file);
|
copyFolder(srcFile, destFile);
|
}
|
} else {
|
Files.copy(source.toPath(), destination.toPath());
|
}
|
}
|
}
|
|
/**
|
* 根据服务名获取端口号
|
* @param serviceId
|
* @return
|
*/
|
private String getGatewayPort(String serviceId) {
|
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
if (!instances.isEmpty()) {
|
ServiceInstance gatewayInstance = instances.get(0);
|
return String.valueOf(gatewayInstance.getPort());
|
}
|
return "8080";
|
}
|
|
/**
|
* 启动、停止、重启命令校验
|
* @param cmd
|
* @return true: 包含高风险命令, false:不包含
|
*/
|
private boolean checkCmdVer(String cmd){
|
if(Func.isEmpty(cmd)){
|
return false;
|
}
|
String[] highRiskCommands = {"rm", "rmdir", "mv", "unlink", "rm -rf", "mv -rf", "dd", "chmod", "chown", "mkfs", "shutdown", "reboot", "kill"};
|
for(String highRiskCmd : highRiskCommands){
|
if(cmd.contains(highRiskCmd)){
|
return true;
|
}
|
}
|
return false;
|
}
|
|
}
|