package com.oying.modules.maint.service.impl; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.oying.modules.maint.domain.Deploy; import com.oying.modules.maint.domain.DeployHistory; import com.oying.modules.maint.domain.Server; import com.oying.modules.maint.domain.dto.DeployQueryCriteria; import com.oying.modules.maint.domain.enums.MsgType; import com.oying.modules.maint.mapper.DeployMapper; import com.oying.modules.maint.mapper.DeployServerMapper; import com.oying.modules.maint.service.websocket.SocketMsg; import com.oying.modules.maint.service.websocket.WebSocketServer; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import com.oying.exception.BadRequestException; import com.oying.modules.maint.domain.App; import com.oying.modules.maint.service.DeployHistoryService; import com.oying.modules.maint.service.DeployService; import com.oying.modules.maint.service.ServerService; import com.oying.modules.maint.util.ExecuteShellUtil; import com.oying.modules.maint.util.ScpClientUtil; import com.oying.utils.FileUtil; import com.oying.utils.PageResult; import com.oying.utils.PageUtil; import com.oying.utils.SecurityUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.*; /** * @author Z * @date 2019-08-24 */ @Slf4j @Service @RequiredArgsConstructor public class DeployServiceImpl extends ServiceImpl implements DeployService { private final String FILE_SEPARATOR = "/"; private final DeployMapper deployMapper; private final DeployServerMapper deployServerMapper; private final ServerService serverService; private final DeployHistoryService deployHistoryService; /** * 循环次数 */ private final Integer count = 30; @Override public PageResult queryAll(DeployQueryCriteria criteria, Page page) { criteria.setOffset(page.offset()); List deploys = deployMapper.findAll(criteria); Long total = deployMapper.countAll(criteria); return PageUtil.toPage(deploys, total); } @Override public List queryAll(DeployQueryCriteria criteria) { return deployMapper.findAll(criteria); } @Override @Transactional(rollbackFor = Exception.class) public void create(Deploy resources) { resources.setAppId(resources.getApp().getId()); save(resources); // 保存关联关系 deployServerMapper.insertData(resources.getId(), resources.getDeploys()); } @Override @Transactional(rollbackFor = Exception.class) public void update(Deploy resources) { Deploy deploy = getById(resources.getId()); deploy.copy(resources); saveOrUpdate(deploy); // 更新关联关系 deployServerMapper.deleteByDeployId(resources.getId()); deployServerMapper.insertData(resources.getId(), resources.getDeploys()); } @Override @Transactional(rollbackFor = Exception.class) public void delete(Set ids) { removeBatchByIds(ids); // 删除关联 deployServerMapper.deleteByDeployIds(ids); } @Override public void deploy(String fileSavePath, Long id) { deployApp(fileSavePath, id); } /** * @param fileSavePath 本机路径 * @param id ID */ private void deployApp(String fileSavePath, Long id) { Deploy deploy = deployMapper.getDeployById(id); if (deploy == null) { sendMsg("部署信息不存在", MsgType.ERROR); throw new BadRequestException("部署信息不存在"); } App app = deploy.getApp(); if (app == null) { sendMsg("包对应应用信息不存在", MsgType.ERROR); throw new BadRequestException("包对应应用信息不存在"); } int port = app.getPort(); //这个是服务器部署路径 String uploadPath = app.getUploadPath(); StringBuilder sb = new StringBuilder(); String msg; Set deploys = deploy.getDeploys(); for (Server server : deploys) { String ip = server.getIp(); ExecuteShellUtil executeShellUtil = getExecuteShellUtil(ip); //判断是否第一次部署 boolean flag = checkFile(executeShellUtil, app); //第一步要确认服务器上有这个目录 executeShellUtil.execute("mkdir -p " + app.getUploadPath()); executeShellUtil.execute("mkdir -p " + app.getBackupPath()); executeShellUtil.execute("mkdir -p " + app.getDeployPath()); //上传文件 msg = String.format("登陆到服务器:%s", ip); ScpClientUtil scpClientUtil = getScpClientUtil(ip); log.info(msg); sendMsg(msg, MsgType.INFO); msg = String.format("上传文件到服务器:%s
目录:%s下,请稍等...", ip, uploadPath); sendMsg(msg, MsgType.INFO); scpClientUtil.putFile(fileSavePath, uploadPath); if (flag) { sendMsg("停止原来应用", MsgType.INFO); //停止应用 stopApp(port, executeShellUtil); sendMsg("备份原来应用", MsgType.INFO); //备份应用 backupApp(executeShellUtil, ip, app.getDeployPath()+FILE_SEPARATOR, app.getName(), app.getBackupPath()+FILE_SEPARATOR, id); } sendMsg("部署应用", MsgType.INFO); //部署文件,并启动应用 String deployScript = app.getDeployScript(); executeShellUtil.execute(deployScript); sleep(3); sendMsg("应用部署中,请耐心等待部署结果,或者稍后手动查看部署状态", MsgType.INFO); int i = 0; boolean result = false; // 由于启动应用需要时间,所以需要循环获取状态,如果超过30次,则认为是启动失败 while (i++ < count){ result = checkIsRunningStatus(port, executeShellUtil); if(result){ break; } // 休眠6秒 sleep(6); } sb.append("服务器:").append(server.getName()).append("
应用:").append(app.getName()); sendResultMsg(result, sb); executeShellUtil.close(); } } private void sleep(int second) { try { Thread.sleep(second * 1000L); } catch (InterruptedException e) { log.error(e.getMessage(),e); } } private void backupApp(ExecuteShellUtil executeShellUtil, String ip, String fileSavePath, String appName, String backupPath, Long id) { String deployDate = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN); StringBuilder sb = new StringBuilder(); backupPath += appName + FILE_SEPARATOR + deployDate + "\n"; sb.append("mkdir -p ").append(backupPath); sb.append("mv -f ").append(fileSavePath); sb.append(appName).append(" ").append(backupPath); log.info("备份应用脚本:" + sb); executeShellUtil.execute(sb.toString()); //还原信息入库 DeployHistory deployHistory = new DeployHistory(); deployHistory.setAppName(appName); deployHistory.setDeployUser(SecurityUtils.getCurrentUsername()); deployHistory.setIp(ip); deployHistory.setDeployId(id); deployHistoryService.create(deployHistory); } /** * 停App * * @param port 端口 * @param executeShellUtil / */ private void stopApp(int port, ExecuteShellUtil executeShellUtil) { //发送停止命令 executeShellUtil.execute(String.format("lsof -i :%d|grep -v \"PID\"|awk '{print \"kill -9\",$2}'|sh", port)); } /** * 指定端口程序是否在运行 * * @param port 端口 * @param executeShellUtil / * @return true 正在运行 false 已经停止 */ private boolean checkIsRunningStatus(int port, ExecuteShellUtil executeShellUtil) { String result = executeShellUtil.executeForResult(String.format("fuser -n tcp %d", port)); return result.indexOf("/tcp:")>0; } private void sendMsg(String msg, MsgType msgType) { try { WebSocketServer.sendInfo(new SocketMsg(msg, msgType), "deploy"); } catch (IOException e) { log.error(e.getMessage(),e); } } @Override public String serverStatus(Deploy resources) { Set servers = resources.getDeploys(); App app = resources.getApp(); for (Server server : servers) { StringBuilder sb = new StringBuilder(); ExecuteShellUtil executeShellUtil = getExecuteShellUtil(server.getIp()); sb.append("服务器:").append(server.getName()).append("
应用:").append(app.getName()); boolean result = checkIsRunningStatus(app.getPort(), executeShellUtil); if (result) { sb.append("
正在运行"); sendMsg(sb.toString(), MsgType.INFO); } else { sb.append("
已停止!"); sendMsg(sb.toString(), MsgType.ERROR); } log.info(sb.toString()); executeShellUtil.close(); } return "执行完毕"; } private boolean checkFile(ExecuteShellUtil executeShellUtil, App app) { String result = executeShellUtil.executeForResult("find " + app.getDeployPath() + " -name " + app.getName()); return result.indexOf(app.getName())>0; } /** * 启动服务 * @param resources / * @return / */ @Override public String startServer(Deploy resources) { Set deploys = resources.getDeploys(); App app = resources.getApp(); for (Server deploy : deploys) { StringBuilder sb = new StringBuilder(); ExecuteShellUtil executeShellUtil = getExecuteShellUtil(deploy.getIp()); //为了防止重复启动,这里先停止应用 stopApp(app.getPort(), executeShellUtil); sb.append("服务器:").append(deploy.getName()).append("
应用:").append(app.getName()); sendMsg("下发启动命令", MsgType.INFO); executeShellUtil.execute(app.getStartScript()); sleep(3); sendMsg("应用启动中,请耐心等待启动结果,或者稍后手动查看运行状态", MsgType.INFO); int i = 0; boolean result = false; // 由于启动应用需要时间,所以需要循环获取状态,如果超过30次,则认为是启动失败 while (i++ < count){ result = checkIsRunningStatus(app.getPort(), executeShellUtil); if(result){ break; } // 休眠6秒 sleep(6); } sendResultMsg(result, sb); log.info(sb.toString()); executeShellUtil.close(); } return "执行完毕"; } /** * 停止服务 * @param resources / * @return / */ @Override public String stopServer(Deploy resources) { Set deploys = resources.getDeploys(); App app = resources.getApp(); for (Server deploy : deploys) { StringBuilder sb = new StringBuilder(); ExecuteShellUtil executeShellUtil = getExecuteShellUtil(deploy.getIp()); sb.append("服务器:").append(deploy.getName()).append("
应用:").append(app.getName()); sendMsg("下发停止命令", MsgType.INFO); //停止应用 stopApp(app.getPort(), executeShellUtil); sleep(1); boolean result = checkIsRunningStatus(app.getPort(), executeShellUtil); if (result) { sb.append("
关闭失败!"); sendMsg(sb.toString(), MsgType.ERROR); } else { sb.append("
关闭成功!"); sendMsg(sb.toString(), MsgType.INFO); } log.info(sb.toString()); executeShellUtil.close(); } return "执行完毕"; } @Override public String serverReduction(DeployHistory resources) { Long deployId = resources.getDeployId(); Deploy deployInfo = getById(deployId); String deployDate = DateUtil.format(resources.getDeployDate(), DatePattern.PURE_DATETIME_PATTERN); App app = deployInfo.getApp(); if (app == null) { sendMsg("应用信息不存在:" + resources.getAppName(), MsgType.ERROR); throw new BadRequestException("应用信息不存在:" + resources.getAppName()); } String backupPath = app.getBackupPath()+FILE_SEPARATOR; backupPath += resources.getAppName() + FILE_SEPARATOR + deployDate; //这个是服务器部署路径 String deployPath = app.getDeployPath(); String ip = resources.getIp(); ExecuteShellUtil executeShellUtil = getExecuteShellUtil(ip); String msg; msg = String.format("登陆到服务器:%s", ip); log.info(msg); sendMsg(msg, MsgType.INFO); sendMsg("停止原来应用", MsgType.INFO); //停止应用 stopApp(app.getPort(), executeShellUtil); //删除原来应用 sendMsg("删除应用", MsgType.INFO); executeShellUtil.execute("rm -rf " + deployPath + FILE_SEPARATOR + resources.getAppName()); //还原应用 sendMsg("还原应用", MsgType.INFO); executeShellUtil.execute("cp -r " + backupPath + "/. " + deployPath); sendMsg("启动应用", MsgType.INFO); executeShellUtil.execute(app.getStartScript()); sendMsg("应用启动中,请耐心等待启动结果,或者稍后手动查看启动状态", MsgType.INFO); int i = 0; boolean result = false; // 由于启动应用需要时间,所以需要循环获取状态,如果超过30次,则认为是启动失败 while (i++ < count){ result = checkIsRunningStatus(app.getPort(), executeShellUtil); if(result){ break; } // 休眠6秒 sleep(6); } StringBuilder sb = new StringBuilder(); sb.append("服务器:").append(ip).append("
应用:").append(resources.getAppName()); sendResultMsg(result, sb); executeShellUtil.close(); return ""; } private ExecuteShellUtil getExecuteShellUtil(String ip) { Server server = serverService.findByIp(ip); if (server == null) { sendMsg("IP对应服务器信息不存在:" + ip, MsgType.ERROR); throw new BadRequestException("IP对应服务器信息不存在:" + ip); } return new ExecuteShellUtil(ip, server.getAccount(), server.getPassword(), server.getPort()); } private ScpClientUtil getScpClientUtil(String ip) { Server server = serverService.findByIp(ip); if (server == null) { sendMsg("IP对应服务器信息不存在:" + ip, MsgType.ERROR); throw new BadRequestException("IP对应服务器信息不存在:" + ip); } return ScpClientUtil.getInstance(ip, server.getPort(), server.getAccount(), server.getPassword()); } private void sendResultMsg(boolean result, StringBuilder sb) { if (result) { sb.append("
启动成功!"); sendMsg(sb.toString(), MsgType.INFO); } else { sb.append("
启动失败!"); sendMsg(sb.toString(), MsgType.ERROR); } } @Override public void download(List deploys, HttpServletResponse response) throws IOException { List> list = new ArrayList<>(); for (Deploy deploy : deploys) { Map map = new LinkedHashMap<>(); map.put("应用名称", deploy.getApp().getName()); map.put("服务器", deploy.getServers()); map.put("部署日期", deploy.getCreateTime()); list.add(map); } FileUtil.downloadExcel(list, response); } }