1. windows 端口号被占用 强制删除
netstat -ano|findstr "8080"
taskkill /F /PID 708
2. 思路
模拟容器平台前端控制后台springboot项目停止,启动,重启。
后台需要两个,一个专门负责管理脚本 vue-back-bat.jar,通过脚本控制另一个后台,一个是具体服务 vue-back-manage.jar 都解压后放在D盘
启动
cmd /c start /b javaw -Dfile.encoding=utf-8 -cp . org.springframework.boot.loader.JarLauncher
3. vue-back-bat后台 设置端口9002
使用commons-exec执行bat脚本
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>1.3</version>
</dependency>
yml 配置异步请求接口超时时间
spring:
mvc:
async:
# 超时设置15s
request-timeout: 15000
封装CommandUtil
/**
* 采用 commons-exec 处理bat,shell脚本或者命令行
*
* @author bestm
*/
@Slf4j
@Component
public class CommandUtil {
/**
* 设置超时时间 10s
*/
private static final long TIMEOUT = 10 * 1000L;
/**
* 新线程以阻塞方式执行子进程,来处理命令行或者脚本
*
* @param command 命令行或者脚本 ipconfig /all, D:/my.bat 111 222
* @return 返回状态码
* @throws IOException
* @throws ExecuteException
*/
public String execCommand(String command) {
CommandLine cmdLine = CommandLine.parse(command);
DefaultExecutor executor = new DefaultExecutor();
// 设置命令执行退出值为0,如果命令成功执行并且没有错误,则返回0
executor.setExitValue(0);
// 输出结果
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(baos, baos);
executor.setStreamHandler(streamHandler);
ExecuteWatchdog watchdog = new ExecuteWatchdog(TIMEOUT);
executor.setWatchdog(watchdog);
try {
int exitValue = executor.execute(cmdLine);
if (exitValue == 0) {
log.info(baos.toString("GBK"));
return exitValue + "";
}
} catch (IOException e) {
return null;
}
return null;
}
/**
* 异步执行
*
* @param command 注意如果是 cmd /c start /b java -cp 后台运行子进程,不能杀死
*/
public String execCommandAsync(String command) {
CommandLine commandLine = CommandLine.parse(command);
DefaultExecutor executor = new DefaultExecutor();
// 输出结果
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//接收异常结果流
ByteArrayOutputStream errStream = new ByteArrayOutputStream();
//10s超时
ExecuteWatchdog watchdog = new ExecuteWatchdog(10 * 1000);
executor.setWatchdog(watchdog);
PumpStreamHandler streamHandler = new PumpStreamHandler(baos, baos);
executor.setStreamHandler(streamHandler);
try {
int code = executor.execute(commandLine);
System.out.println("result code: " + code);
// 不同操作系统注意编码,否则结果乱码
String suc = baos.toString("GBK");
String err = errStream.toString("GBK");
log.info(err);
return suc;
} catch (Exception e) {
watchdog.killedProcess();
}
return "";
}
}
Controller
@Slf4j
@RestController
@RequestMapping("/cmd")
public class CmdController {
@Autowired
private SyConfigFileUtil syConfigFileUtil;
@Autowired
private CommandUtil commandUtil;
private static final String RESTART_CMD = "restart.cmd";
private static final String STOP_CMD = "stop.cmd";
/**
* 重启项目vue-back-manage
*
* @return 无法获取脚本执行结果,因为是后台子进程执行,子进程挂起
*/
@PostMapping("/restart")
public Callable<String> restart() {
return () -> {
try {
String command = "cmd /c start /b " + syConfigFileUtil.getClassPath() + "/" + RESTART_CMD;
log.info(command);
return commandUtil.execCommandAsync(command);
} catch (Exception exception) {
log.info("超时返回");
return "0";
}
};
}
/**
* 停止项目vue-back-manage
*
* @return
*/
@PostMapping("/stop")
public String stop() {
String command = "cmd /c start /b " + syConfigFileUtil.getClassPath() + "/" + STOP_CMD;
log.info(command);
String code = commandUtil.execCommand(command);
log.info("退出码:{}", code);
return code;
}
}
stop.bat
set num=9001
for /f "tokens=5" %%a in ('netstat /ano ^| findstr %num%') do (
if %%a neq 0 (
echo 端口号9001的pid为%%a
taskkill /F /pid %%a
goto _break
) else (
echo 端口号9001未被占用
)
)
rem 结束for循环
:_break
```
restart.bat
```bat
set num=9001
for /f "tokens=5" %%a in ('netstat /ano ^| findstr %num%') do (
if %%a neq 0 (
echo 端口号9001的pid为%%a
taskkill /F /pid %%a
goto _break
) else (
echo 端口号9001未被占用
)
)
rem 结束for循环
:_break
rem 切换盘符
D:
rem 这里切换到项目目录内,否则会导致application.pid无法生成
cd D:/vue-back-manage
rem 后台执行 启动springboot项目 cmd /c start /b
java -Dfile.encoding=utf-8 -cp . org.springframework.boot.loader.JarLauncher
```
#### 4. vue-back-manage后台 设置端口9001
获取项目路径工具类SyConfigFileUtil
```java
/**
* 系统配置文件读取
*
* @author liuxb
* @date 2022/5/22 11:47
**/
@Slf4j
@Component
public class SyConfigFileUtil {
@Value("classpath:restart.cmd")
private Resource resource;
/**
* 获取类路径
*
* @return
*/
public String getClassPath() {
try {
return resource.getFile().getParentFile().getAbsolutePath();
} catch (IOException e) {
return "";
}
}
/**
* 读取文件内容
*
* @param filePath 相对项目的文件路径
* @return
*/
public String getFileContent(String filePath) {
File file = new File(getClassPath(), filePath);
log.info("路径:{}", file.getPath());
String content = null;
try (InputStream inputStream = new FileInputStream(file)) {
content = IOUtils.toString(inputStream);
} catch (IOException e) {
throw new BizException("读取文件失败");
}
return content;
}
/**
* 修改文件内容
*
* @param filePath 相对项目的文件路径
* @param content 文件内容
* @return
*/
public boolean updateFile(String filePath, String content) {
File file = new File(getClassPath(), filePath);
log.info("路径:{}", file.getPath());
try (OutputStream out = new FileOutputStream(file)) {
IOUtils.write(content, out, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new BizException("写入文件失败");
}
return true;
}
}
```
读取pidController
```java
@Autowired
private SyConfigFileUtil syConfigFileUtil;
private static final String APPLICATION_PID = "../../application.pid";
@PostMapping("/getPID")
public String getPID() {
return syConfigFileUtil.getFileContent(APPLICATION_PID);
}
```
读取ymlController
```java
@Slf4j
@Validated
@RestController
@RequestMapping("/yml")
public class YmlController {
@Autowired
private SyConfigFileUtil syConfigFileUtil;
private static final String CONFIG_FILE = "application.yml";
/**
* 读取application.yml内容
*
* @return
*/
@PostMapping("/getApplication")
public String getApplication() {
return syConfigFileUtil.getFileContent(CONFIG_FILE);
}
/**
* 修改application.yml内容
*
* @param content application.yml内容
* @return
*/
@PostMapping("/updateApplication")
public boolean updateApplication(@RequestSingleBody @NotBlank(message = "内容不能为空") String content) {
return syConfigFileUtil.updateFile(CONFIG_FILE, content);
}
}
```