1.0
xin
2025-04-15 719d2454c260d49cb9f92a1a73b31d98c1083d82
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
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<DeployMapper, Deploy> 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<Deploy> queryAll(DeployQueryCriteria criteria, Page<Object> page) {
        criteria.setOffset(page.offset());
        List<Deploy> deploys = deployMapper.findAll(criteria);
        Long total = deployMapper.countAll(criteria);
        return PageUtil.toPage(deploys, total);
    }
 
    @Override
    public List<Deploy> 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<Long> 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<Server> 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<br>目录:%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("<br>应用:").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<Server> 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("<br>应用:").append(app.getName());
            boolean result = checkIsRunningStatus(app.getPort(), executeShellUtil);
            if (result) {
                sb.append("<br>正在运行");
                sendMsg(sb.toString(), MsgType.INFO);
            } else {
                sb.append("<br>已停止!");
                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<Server> 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("<br>应用:").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<Server> 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("<br>应用:").append(app.getName());
            sendMsg("下发停止命令", MsgType.INFO);
            //停止应用
            stopApp(app.getPort(), executeShellUtil);
            sleep(1);
            boolean result = checkIsRunningStatus(app.getPort(), executeShellUtil);
            if (result) {
                sb.append("<br>关闭失败!");
                sendMsg(sb.toString(), MsgType.ERROR);
            } else {
                sb.append("<br>关闭成功!");
                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("<br>应用:").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("<br>启动成功!");
            sendMsg(sb.toString(), MsgType.INFO);
        } else {
            sb.append("<br>启动失败!");
            sendMsg(sb.toString(), MsgType.ERROR);
        }
    }
 
    @Override
    public void download(List<Deploy> deploys, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (Deploy deploy : deploys) {
            Map<String,Object> map = new LinkedHashMap<>();
            map.put("应用名称", deploy.getApp().getName());
            map.put("服务器", deploy.getServers());
            map.put("部署日期", deploy.getCreateTime());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }
}