2024-08-22 09:22:52 +08:00
|
|
|
package net.geedge.api.controller;
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.codec.Base32Codec;
|
2024-11-14 14:28:30 +08:00
|
|
|
import cn.hutool.core.collection.CollUtil;
|
2024-11-18 14:50:34 +08:00
|
|
|
import cn.hutool.core.collection.ListUtil;
|
2024-10-18 17:17:53 +08:00
|
|
|
import cn.hutool.core.io.FileUtil;
|
2024-10-17 17:10:45 +08:00
|
|
|
import cn.hutool.core.thread.ThreadUtil;
|
|
|
|
|
import cn.hutool.log.Log;
|
2024-08-22 09:22:52 +08:00
|
|
|
import jakarta.servlet.http.HttpServletResponse;
|
2024-09-04 14:19:02 +08:00
|
|
|
import net.geedge.api.entity.EnvApiYml;
|
2024-08-22 09:22:52 +08:00
|
|
|
import net.geedge.api.util.AdbUtil;
|
2024-10-31 18:07:04 +08:00
|
|
|
import net.geedge.api.util.CommandExec;
|
2024-08-22 09:22:52 +08:00
|
|
|
import net.geedge.common.*;
|
2024-11-12 15:17:00 +08:00
|
|
|
import net.lingala.zip4j.ZipFile;
|
|
|
|
|
import net.lingala.zip4j.model.ZipParameters;
|
|
|
|
|
import net.lingala.zip4j.model.enums.CompressionLevel;
|
|
|
|
|
import net.lingala.zip4j.model.enums.CompressionMethod;
|
2024-08-22 09:22:52 +08:00
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
|
2024-10-31 17:02:52 +08:00
|
|
|
import java.io.*;
|
2024-11-12 15:17:00 +08:00
|
|
|
import java.util.*;
|
2024-08-22 09:22:52 +08:00
|
|
|
|
|
|
|
|
@RestController
|
2024-09-04 14:19:02 +08:00
|
|
|
@RequestMapping("/api/v1/env")
|
2024-08-22 09:22:52 +08:00
|
|
|
public class APIController {
|
|
|
|
|
|
2024-10-17 17:10:45 +08:00
|
|
|
private final static Log log = Log.get();
|
|
|
|
|
|
2024-08-22 09:22:52 +08:00
|
|
|
private final AdbUtil adbUtil;
|
|
|
|
|
|
2024-11-14 14:28:30 +08:00
|
|
|
static final List<Thread> ACTIVE_TASKS = Collections.synchronizedList(new ArrayList<>());
|
|
|
|
|
|
2024-08-22 09:22:52 +08:00
|
|
|
@Autowired
|
2024-09-04 14:19:02 +08:00
|
|
|
public APIController(EnvApiYml envApiYml) {
|
2024-10-31 18:07:04 +08:00
|
|
|
this.adbUtil = AdbUtil.getInstance(envApiYml.getAdb(), new CommandExec(null));
|
2024-08-22 09:22:52 +08:00
|
|
|
}
|
|
|
|
|
|
2024-10-31 18:07:04 +08:00
|
|
|
@Autowired
|
|
|
|
|
private EnvApiYml apiYml;
|
|
|
|
|
|
2024-08-22 09:22:52 +08:00
|
|
|
@GetMapping("/status")
|
|
|
|
|
public R status() {
|
|
|
|
|
return R.ok(adbUtil.status());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@PostMapping("/file")
|
|
|
|
|
public R push(@RequestParam(value = "file") MultipartFile file, @RequestParam String path) throws IOException {
|
|
|
|
|
File tempFile = null;
|
|
|
|
|
try {
|
|
|
|
|
tempFile = T.FileUtil.file(Constant.TEMP_PATH, file.getOriginalFilename());
|
|
|
|
|
file.transferTo(tempFile);
|
|
|
|
|
|
|
|
|
|
AdbUtil.CommandResult result = adbUtil.push(tempFile.getAbsolutePath(), path);
|
|
|
|
|
if (0 != result.exitCode()) {
|
|
|
|
|
return R.error(result.output());
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
T.FileUtil.del(tempFile);
|
|
|
|
|
}
|
|
|
|
|
return R.ok();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@GetMapping("/file/{fileId}")
|
|
|
|
|
public void pull(@PathVariable String fileId, HttpServletResponse response) throws IOException {
|
|
|
|
|
byte[] decode = Base32Codec.Base32Decoder.DECODER.decode(fileId);
|
|
|
|
|
String filePath = T.StrUtil.str(decode, T.CharsetUtil.CHARSET_UTF_8);
|
|
|
|
|
String fileName = T.FileUtil.getName(filePath);
|
|
|
|
|
|
|
|
|
|
File tempFile = T.FileUtil.file(Constant.TEMP_PATH, fileName);
|
|
|
|
|
try {
|
2024-10-31 18:07:04 +08:00
|
|
|
AdbUtil.CommandResult result = adbUtil.pull(filePath, tempFile.getAbsolutePath());
|
2024-08-22 09:22:52 +08:00
|
|
|
if (0 != result.exitCode()) {
|
|
|
|
|
throw new APIException(result.output());
|
|
|
|
|
}
|
|
|
|
|
if (T.FileUtil.isDirectory(tempFile)) {
|
|
|
|
|
File zip = T.ZipUtil.zip(tempFile);
|
|
|
|
|
try {
|
|
|
|
|
T.ResponseUtil.downloadFile(response, zip.getName(), T.FileUtil.readBytes(zip));
|
|
|
|
|
} finally {
|
|
|
|
|
T.FileUtil.del(zip);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
T.ResponseUtil.downloadFile(response, fileName, T.FileUtil.readBytes(tempFile));
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
T.FileUtil.del(tempFile);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@GetMapping("/file")
|
|
|
|
|
public R listDir(@RequestParam(defaultValue = "/") String path) {
|
|
|
|
|
List<Map> listDir = adbUtil.listDir(path);
|
|
|
|
|
Map<Object, Object> data = T.MapUtil.builder()
|
|
|
|
|
.put("path", path)
|
|
|
|
|
.put("records", listDir)
|
|
|
|
|
.build();
|
|
|
|
|
return R.ok(data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@GetMapping("/app")
|
|
|
|
|
public R listApp(@RequestParam(required = false) String arg) {
|
|
|
|
|
return R.ok().putData("records", adbUtil.listApp(arg));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@PostMapping("/app")
|
|
|
|
|
public R install(@RequestParam(value = "file", required = false) MultipartFile file,
|
|
|
|
|
@RequestParam(required = false) String path) throws IOException {
|
|
|
|
|
if (file != null) {
|
|
|
|
|
File tempFile = null;
|
|
|
|
|
try {
|
|
|
|
|
tempFile = T.FileUtil.file(Constant.TEMP_PATH, file.getOriginalFilename());
|
|
|
|
|
file.transferTo(tempFile);
|
|
|
|
|
|
2024-10-31 18:07:04 +08:00
|
|
|
AdbUtil.CommandResult result = adbUtil.install(tempFile.getAbsolutePath(), true, true);
|
2024-08-22 09:22:52 +08:00
|
|
|
if (0 != result.exitCode()) {
|
|
|
|
|
throw new APIException(result.output());
|
|
|
|
|
}
|
|
|
|
|
return R.ok();
|
|
|
|
|
} finally {
|
|
|
|
|
T.FileUtil.del(tempFile);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (T.StrUtil.isNotEmpty(path)) {
|
2024-10-31 18:07:04 +08:00
|
|
|
AdbUtil.CommandResult result = adbUtil.install(path, true, true);
|
2024-08-22 09:22:52 +08:00
|
|
|
if (0 != result.exitCode()) {
|
|
|
|
|
throw new APIException(result.output());
|
|
|
|
|
}
|
|
|
|
|
return R.ok();
|
|
|
|
|
}
|
|
|
|
|
return R.error(RCode.BAD_REQUEST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@DeleteMapping("/app")
|
|
|
|
|
public R uninstall(@RequestParam String packageName) {
|
|
|
|
|
AdbUtil.CommandResult result = adbUtil.uninstall(packageName);
|
|
|
|
|
if (0 != result.exitCode()) {
|
|
|
|
|
throw new APIException(result.output());
|
|
|
|
|
}
|
|
|
|
|
return R.ok();
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-27 15:46:51 +08:00
|
|
|
@GetMapping("/pcap")
|
|
|
|
|
public R listTcpdump() {
|
|
|
|
|
return R.ok().putData("records", adbUtil.listTcpdump());
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-22 09:22:52 +08:00
|
|
|
@PostMapping("/pcap")
|
|
|
|
|
public R startTcpdump(@RequestParam(required = false, defaultValue = "") String packageName) {
|
2024-10-31 18:07:04 +08:00
|
|
|
AdbUtil.CommandResult result = adbUtil.startTcpdump(packageName);
|
2024-08-22 09:22:52 +08:00
|
|
|
if (0 != result.exitCode()) {
|
|
|
|
|
throw new APIException("exec tcpdump error");
|
|
|
|
|
}
|
|
|
|
|
return R.ok().putData("id", result.output());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@DeleteMapping("/pcap")
|
2024-09-10 16:54:16 +08:00
|
|
|
public synchronized void stopTcpdump(@RequestParam String id,
|
2024-10-31 17:02:52 +08:00
|
|
|
@RequestParam(required = false, defaultValue = "false") Boolean returnFile,
|
|
|
|
|
HttpServletResponse response) throws IOException {
|
2024-10-31 18:07:04 +08:00
|
|
|
AdbUtil.CommandResult result = adbUtil.stopTcpdump(id);
|
2024-08-22 09:22:52 +08:00
|
|
|
if (0 != result.exitCode()) {
|
|
|
|
|
throw new APIException(result.output());
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-09 13:55:39 +08:00
|
|
|
String filePath = result.output();
|
|
|
|
|
try {
|
|
|
|
|
if (returnFile) {
|
|
|
|
|
// response pcap file
|
|
|
|
|
File tempFile = T.FileUtil.file(Constant.TEMP_PATH, id + ".pcap");
|
|
|
|
|
try {
|
|
|
|
|
if (T.StrUtil.isEmpty(filePath)) {
|
|
|
|
|
throw new APIException(RCode.NOT_EXISTS);
|
|
|
|
|
}
|
2024-10-31 18:07:04 +08:00
|
|
|
AdbUtil.CommandResult pulled = adbUtil.pull(filePath, tempFile.getAbsolutePath());
|
2024-09-09 13:55:39 +08:00
|
|
|
if (0 != pulled.exitCode()) {
|
|
|
|
|
throw new APIException(pulled.output());
|
|
|
|
|
}
|
|
|
|
|
T.ResponseUtil.downloadFile(response, tempFile.getName(), T.FileUtil.readBytes(tempFile));
|
|
|
|
|
} finally {
|
|
|
|
|
T.FileUtil.del(tempFile);
|
2024-08-22 09:22:52 +08:00
|
|
|
}
|
2024-09-09 13:55:39 +08:00
|
|
|
} else {
|
|
|
|
|
// response taskid
|
|
|
|
|
response.getWriter().write(T.JSONUtil.toJsonStr(R.ok().putData("id", id)));
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
if (T.StrUtil.isNotEmpty(filePath)) {
|
|
|
|
|
// remove pcap file
|
2024-10-31 18:07:04 +08:00
|
|
|
adbUtil.execShellCommand(String.format("shell rm -rf %s", filePath));
|
2024-08-22 09:22:52 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-27 15:46:51 +08:00
|
|
|
|
|
|
|
|
@PostMapping("/shell")
|
|
|
|
|
public R execShellCmd(@RequestBody Map<String, Object> requestBody) {
|
|
|
|
|
String cmd = T.MapUtil.getStr(requestBody, "cmd", "");
|
|
|
|
|
if (T.StrUtil.isEmpty(cmd)) {
|
|
|
|
|
return R.error(RCode.BAD_REQUEST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Integer timeout = T.MapUtil.getInt(requestBody, "timeout", 10);
|
|
|
|
|
return R.ok().putData("result", adbUtil.execShellCommand(cmd, timeout));
|
|
|
|
|
}
|
2024-09-12 15:51:50 +08:00
|
|
|
|
|
|
|
|
@GetMapping("/acl")
|
|
|
|
|
public R listAcl() {
|
|
|
|
|
return R.ok().putData("records", adbUtil.listAcl());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@PostMapping("/acl")
|
|
|
|
|
public R addAcl(@RequestBody Map<String, Object> requestBody) {
|
|
|
|
|
String ip = T.MapUtil.getStr(requestBody, "ip");
|
|
|
|
|
String port = T.MapUtil.getStr(requestBody, "port");
|
|
|
|
|
if (T.StrUtil.isAllEmpty(ip, port)) {
|
|
|
|
|
return R.error(RCode.BAD_REQUEST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String protocol = T.MapUtil.getStr(requestBody, "protocol", "all");
|
|
|
|
|
if (!T.StrUtil.equalsAny(protocol, "tcp", "udp", "all")) {
|
|
|
|
|
return R.error(RCode.BAD_REQUEST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ("all".equals(protocol) && T.StrUtil.isEmpty(ip)) {
|
|
|
|
|
return R.error(RCode.BAD_REQUEST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
adbUtil.addAcl(protocol, ip, port);
|
|
|
|
|
return R.ok().putData("records", adbUtil.listAcl());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@DeleteMapping("/acl")
|
|
|
|
|
public R deleteAcl(@RequestBody Map<String, Object> requestBody) {
|
|
|
|
|
String ip = T.MapUtil.getStr(requestBody, "ip");
|
|
|
|
|
String port = T.MapUtil.getStr(requestBody, "port");
|
|
|
|
|
if (T.StrUtil.isAllEmpty(ip, port)) {
|
|
|
|
|
return R.error(RCode.BAD_REQUEST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String protocol = T.MapUtil.getStr(requestBody, "protocol", "all");
|
|
|
|
|
if (!T.StrUtil.equalsAny(protocol, "tcp", "udp", "all")) {
|
|
|
|
|
return R.error(RCode.BAD_REQUEST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ("all".equals(protocol) && T.StrUtil.isEmpty(ip)) {
|
|
|
|
|
return R.error(RCode.BAD_REQUEST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
adbUtil.deleteAcl(protocol, ip, port);
|
|
|
|
|
return R.ok().putData("records", adbUtil.listAcl());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@DeleteMapping("/acl/flush")
|
|
|
|
|
public R flushAcl() {
|
|
|
|
|
AdbUtil.CommandResult result = adbUtil.flushAcl();
|
|
|
|
|
if (0 != result.exitCode()) {
|
|
|
|
|
return R.error(result.output());
|
|
|
|
|
}
|
|
|
|
|
return R.ok();
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-17 17:10:45 +08:00
|
|
|
@PostMapping("/playbook")
|
2024-11-01 15:08:56 +08:00
|
|
|
public R execPlaybook(@RequestParam("file") MultipartFile file,
|
2024-10-31 17:02:52 +08:00
|
|
|
@RequestParam("packageName") String packageName,
|
2024-11-18 14:50:34 +08:00
|
|
|
@RequestParam("id") String id,
|
|
|
|
|
@RequestParam("reInstall") Boolean reInstall,
|
|
|
|
|
@RequestParam("clearCache") Boolean clearCache,
|
|
|
|
|
@RequestParam("unInstall") Boolean unInstall) {
|
2024-10-31 17:02:52 +08:00
|
|
|
File apkFile = null;
|
2024-10-18 17:17:53 +08:00
|
|
|
File playbookAirDir = null;
|
2024-10-31 17:02:52 +08:00
|
|
|
File destination = null;
|
|
|
|
|
try {
|
|
|
|
|
File playbookDir = T.FileUtil.file(Constant.TEMP_PATH, id);
|
|
|
|
|
destination = T.FileUtil.file(Constant.TEMP_PATH, id, file.getName());
|
|
|
|
|
T.FileUtil.writeBytes(file.getInputStream().readAllBytes(), destination);
|
|
|
|
|
|
|
|
|
|
// unzip file
|
|
|
|
|
T.ZipUtil.unzip(destination, playbookDir);
|
|
|
|
|
|
|
|
|
|
// apk
|
|
|
|
|
apkFile = Arrays.stream(playbookDir.listFiles(new FilenameFilter() {
|
|
|
|
|
@Override
|
|
|
|
|
public boolean accept(File dir, String name) {
|
|
|
|
|
return name.endsWith(".apk");
|
|
|
|
|
}
|
|
|
|
|
})).findFirst().get();
|
|
|
|
|
|
|
|
|
|
// playbook zip
|
|
|
|
|
File playbook = Arrays.stream(playbookDir.listFiles(new FilenameFilter() {
|
|
|
|
|
@Override
|
|
|
|
|
public boolean accept(File dir, String name) {
|
|
|
|
|
return name.endsWith(".zip") && !name.equals(file.getName());
|
|
|
|
|
}
|
|
|
|
|
})).findFirst().get();
|
|
|
|
|
|
|
|
|
|
// unzip playbook zip
|
|
|
|
|
T.ZipUtil.unzip(playbook, playbookDir);
|
|
|
|
|
playbookAirDir = Arrays.stream(playbookDir.listFiles(new FileFilter() {
|
|
|
|
|
@Override
|
|
|
|
|
public boolean accept(File pathname) {
|
|
|
|
|
return pathname.getName().endsWith(".air");
|
|
|
|
|
}
|
|
|
|
|
})).findFirst().get();
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.error(e.getMessage());
|
2024-11-12 15:17:00 +08:00
|
|
|
throw new APIException(RCode.ERROR);
|
2024-10-31 17:02:52 +08:00
|
|
|
} finally {
|
|
|
|
|
T.FileUtil.del(destination);
|
2024-10-17 17:10:45 +08:00
|
|
|
}
|
2024-11-18 14:50:34 +08:00
|
|
|
|
|
|
|
|
PlaybookRunnable playbookRunnable = new PlaybookRunnable(apiYml, apkFile, playbookAirDir, id, packageName, reInstall, clearCache, unInstall);
|
2024-11-14 14:28:30 +08:00
|
|
|
playbookRunnable.setName(T.StrUtil.concat(true, id, "-", apkFile.getName()));
|
2024-10-17 17:10:45 +08:00
|
|
|
ThreadUtil.execAsync(playbookRunnable);
|
2024-10-31 17:02:52 +08:00
|
|
|
return R.ok();
|
2024-10-17 17:10:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@GetMapping("/playbook/{id}")
|
2024-10-31 17:02:52 +08:00
|
|
|
public R checkJobResult(@PathVariable("id") String id){
|
2024-10-17 17:10:45 +08:00
|
|
|
if (T.StrUtil.isEmpty(id)) {
|
|
|
|
|
throw new APIException(RCode.BAD_REQUEST);
|
|
|
|
|
}
|
2024-11-12 15:17:00 +08:00
|
|
|
File statusFile = FileUtil.file(Constant.TEMP_PATH, id, "result.json");
|
|
|
|
|
String status = T.FileUtil.readString(statusFile, "UTF-8");
|
|
|
|
|
return R.ok().putData(status);
|
2024-10-31 17:02:52 +08:00
|
|
|
}
|
|
|
|
|
|
2024-11-14 14:28:30 +08:00
|
|
|
@DeleteMapping("/playbook/{id}")
|
|
|
|
|
public R cancel(@PathVariable("id") String id){
|
|
|
|
|
if (T.StrUtil.isEmpty(id)) {
|
|
|
|
|
throw new APIException(RCode.BAD_REQUEST);
|
|
|
|
|
}
|
|
|
|
|
if (CollUtil.isNotEmpty(ACTIVE_TASKS)) {
|
|
|
|
|
ACTIVE_TASKS.stream().filter(thread -> T.StrUtil.startWith(thread.getName(), id)).forEach(thread -> {
|
|
|
|
|
log.info(String.format("playbook thread: %s has been canceled", thread.getName()));
|
|
|
|
|
thread.interrupt();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return R.ok();
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-31 17:02:52 +08:00
|
|
|
|
|
|
|
|
@GetMapping("/playbook/{id}/log")
|
|
|
|
|
public R getJobResultLog(@PathVariable("id") String id,
|
|
|
|
|
@RequestParam("offset") Integer offset){
|
|
|
|
|
if (T.StrUtil.isEmpty(id)) {
|
|
|
|
|
throw new APIException(RCode.BAD_REQUEST);
|
2024-10-17 17:10:45 +08:00
|
|
|
}
|
2024-10-31 17:02:52 +08:00
|
|
|
// log file
|
|
|
|
|
File logFile = T.FileUtil.file(Constant.TEMP_PATH, id, "result.log");
|
|
|
|
|
HashMap<Object, Object> result = T.MapUtil.newHashMap(false);
|
2024-11-05 09:46:27 +08:00
|
|
|
|
2024-10-31 17:02:52 +08:00
|
|
|
try (RandomAccessFile raf = new RandomAccessFile(logFile, "r")) {
|
2024-11-05 09:46:27 +08:00
|
|
|
if (offset < raf.length()) {
|
|
|
|
|
raf.seek(offset);
|
|
|
|
|
byte[] bytes = new byte[(int)raf.length() - offset];
|
|
|
|
|
raf.readFully(bytes);
|
|
|
|
|
String content = new String(bytes);
|
|
|
|
|
result.put("content", content);
|
|
|
|
|
result.put("length", bytes.length);
|
|
|
|
|
result.put("offset", offset + bytes.length);
|
|
|
|
|
}
|
2024-10-31 17:02:52 +08:00
|
|
|
} catch (IOException e) {
|
|
|
|
|
log.error("getJobResultLog error", e);
|
|
|
|
|
throw new APIException(RCode.ERROR);
|
|
|
|
|
}
|
|
|
|
|
return R.ok().putData(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@GetMapping("/playbook/{id}/artifact")
|
|
|
|
|
public void getJobResultArtifact(@PathVariable("id") String id, HttpServletResponse response) throws IOException {
|
|
|
|
|
if (T.StrUtil.isEmpty(id)) {
|
|
|
|
|
throw new APIException(RCode.BAD_REQUEST);
|
|
|
|
|
}
|
|
|
|
|
// job dir
|
|
|
|
|
File jobResult = T.FileUtil.file(Constant.TEMP_PATH, id);
|
2024-10-31 18:07:04 +08:00
|
|
|
File zipFile = T.FileUtil.file(Constant.TEMP_PATH, id, T.StrUtil.concat(true, id, ".zip"));
|
2024-10-31 17:02:52 +08:00
|
|
|
File[] files = jobResult.listFiles(new FilenameFilter() {
|
|
|
|
|
@Override
|
|
|
|
|
public boolean accept(File dir, String name) {
|
|
|
|
|
return name.endsWith(".log") || name.endsWith(".pcap");
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-11-12 15:17:00 +08:00
|
|
|
ZipFile zip = null;
|
|
|
|
|
try {
|
|
|
|
|
zip = new ZipFile(zipFile);
|
2024-10-31 17:02:52 +08:00
|
|
|
|
2024-11-12 15:17:00 +08:00
|
|
|
ZipParameters parameters = new ZipParameters();
|
|
|
|
|
parameters.setCompressionMethod(CompressionMethod.DEFLATE); // 压缩方法
|
|
|
|
|
parameters.setCompressionLevel(CompressionLevel.FASTEST); // 压缩级别,选项有 FASTEST、ULTRA 等
|
|
|
|
|
|
|
|
|
|
// 添加文件到 ZIP
|
|
|
|
|
for (File file : files) {
|
|
|
|
|
zip.addFile(file, parameters);
|
|
|
|
|
}
|
|
|
|
|
T.ResponseUtil.downloadFile(response, zipFile.getName(), T.FileUtil.readBytes(zipFile.getPath()));
|
|
|
|
|
} finally {
|
|
|
|
|
zip.close();
|
|
|
|
|
}
|
2024-10-17 17:10:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class PlaybookRunnable extends Thread {
|
|
|
|
|
|
|
|
|
|
private AdbUtil adbUtil;
|
2024-10-31 18:07:04 +08:00
|
|
|
private EnvApiYml envApiYml;
|
2024-10-17 17:10:45 +08:00
|
|
|
private String tid;
|
|
|
|
|
private File apkFile;
|
|
|
|
|
private String packageName;
|
|
|
|
|
private File playbookDir;
|
2024-11-18 14:50:34 +08:00
|
|
|
private boolean reInstall;
|
|
|
|
|
private boolean clearCache;
|
|
|
|
|
private boolean unInstall;
|
2024-11-14 14:28:30 +08:00
|
|
|
private boolean interrupt;
|
2024-10-17 17:10:45 +08:00
|
|
|
|
2024-11-18 14:50:34 +08:00
|
|
|
public PlaybookRunnable(EnvApiYml envApiYml, File apkFile, File playbookDir, String tid, String packageName, Boolean reInstall, Boolean clearCache, Boolean unInstall) {
|
2024-10-31 18:07:04 +08:00
|
|
|
this.envApiYml = envApiYml;
|
2024-10-17 17:10:45 +08:00
|
|
|
this.tid = tid;
|
|
|
|
|
this.apkFile = apkFile;
|
|
|
|
|
this.packageName = packageName;
|
|
|
|
|
this.playbookDir = playbookDir;
|
2024-11-18 14:50:34 +08:00
|
|
|
this.reInstall = reInstall;
|
|
|
|
|
this.clearCache = clearCache;
|
|
|
|
|
this.unInstall = unInstall;
|
2024-11-14 14:28:30 +08:00
|
|
|
this.interrupt = false;
|
2024-10-17 17:10:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
2024-11-14 14:28:30 +08:00
|
|
|
ACTIVE_TASKS.add(this);
|
2024-10-31 17:02:52 +08:00
|
|
|
File logFile = FileUtil.file(Constant.TEMP_PATH, tid, "result.log");
|
2024-11-12 15:17:00 +08:00
|
|
|
File statusFile = FileUtil.file(Constant.TEMP_PATH, tid, "result.json");
|
|
|
|
|
AdbUtil.CommandResult tcpdumpPackage = null;
|
|
|
|
|
AdbUtil.CommandResult tcpdumpAll = null;
|
2024-10-17 17:10:45 +08:00
|
|
|
try {
|
|
|
|
|
Map resultMap = T.MapUtil.builder()
|
|
|
|
|
.put("status", "running")
|
|
|
|
|
.build();
|
2024-11-12 15:17:00 +08:00
|
|
|
T.FileUtil.writeString(T.JSONUtil.toJsonStr(resultMap), statusFile, "UTF-8");
|
2024-10-17 17:10:45 +08:00
|
|
|
|
2024-11-14 14:28:30 +08:00
|
|
|
if (interrupt) return;
|
2024-11-18 14:50:34 +08:00
|
|
|
T.FileUtil.appendString(String.format("Running with %s:%s Android Simulator \n", envApiYml.getAdb().getHost(), envApiYml.getAdb().getPort()), logFile, "UTF-8");
|
|
|
|
|
adbUtil = new AdbUtil(envApiYml.getAdb(), new CommandExec(logFile));
|
|
|
|
|
|
|
|
|
|
// Check if the package is installed
|
|
|
|
|
if (interrupt) return;
|
|
|
|
|
boolean packageIsInstall = adbUtil.findPackageInstall(packageName);
|
|
|
|
|
if (packageIsInstall){
|
|
|
|
|
if (!reInstall){
|
|
|
|
|
// install apk
|
|
|
|
|
if (interrupt) return;
|
|
|
|
|
AdbUtil.CommandResult install = adbUtil.install(apkFile.getAbsolutePath(), true, true);
|
|
|
|
|
if (0 != install.exitCode()) {
|
|
|
|
|
T.FileUtil.appendString(String.format("ERROR: Install apk failed: exit code %s \n", install.exitCode()), logFile, "UTF-8");
|
|
|
|
|
throw new APIException(install.output());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}else {
|
|
|
|
|
// install apk
|
|
|
|
|
if (interrupt) return;
|
|
|
|
|
AdbUtil.CommandResult install = adbUtil.install(apkFile.getAbsolutePath(), true, true);
|
|
|
|
|
if (0 != install.exitCode()) {
|
|
|
|
|
T.FileUtil.appendString(String.format("ERROR: Install apk failed: exit code %s \n", install.exitCode()), logFile, "UTF-8");
|
|
|
|
|
throw new APIException(install.output());
|
|
|
|
|
}
|
2024-10-17 17:10:45 +08:00
|
|
|
}
|
|
|
|
|
|
2024-11-18 14:50:34 +08:00
|
|
|
//Close other apps
|
|
|
|
|
if (interrupt) return;
|
|
|
|
|
List<String> packageNameList = adbUtil.findPackageNameList();
|
|
|
|
|
this.closeApp(packageNameList, packageName);
|
|
|
|
|
|
2024-11-12 15:17:00 +08:00
|
|
|
// clear app data
|
2024-11-14 14:28:30 +08:00
|
|
|
if (interrupt) return;
|
2024-11-18 14:50:34 +08:00
|
|
|
if (clearCache){
|
|
|
|
|
AdbUtil.CommandResult clearData = adbUtil.clearAppData(packageName);
|
|
|
|
|
if (0 != clearData.exitCode()) {
|
|
|
|
|
T.FileUtil.appendString(String.format("ERROR: Clear %s data error: exit code %s \n", packageName, clearData.exitCode()), logFile, "UTF-8");
|
|
|
|
|
throw new APIException(clearData.output());
|
|
|
|
|
}
|
2024-10-17 17:10:45 +08:00
|
|
|
}
|
|
|
|
|
|
2024-11-18 14:50:34 +08:00
|
|
|
// Launch the app
|
|
|
|
|
if (interrupt) return;
|
|
|
|
|
adbUtil.startApp(packageName);
|
|
|
|
|
|
2024-11-12 15:17:00 +08:00
|
|
|
// star tcpdump: package name
|
2024-11-14 14:28:30 +08:00
|
|
|
if (interrupt) return;
|
2024-11-12 15:17:00 +08:00
|
|
|
tcpdumpPackage = adbUtil.startTcpdump(packageName);
|
|
|
|
|
if (0 != tcpdumpPackage.exitCode()) {
|
|
|
|
|
T.FileUtil.appendString(String.format("ERROR: Start tcpdump %s failed: exit code %s \n", packageName, tcpdumpPackage.exitCode()), logFile, "UTF-8");
|
|
|
|
|
throw new APIException(String.format("tcpdump %s error", packageName));
|
2024-10-17 17:10:45 +08:00
|
|
|
}
|
|
|
|
|
|
2024-11-12 15:17:00 +08:00
|
|
|
// star tcpdump: all
|
2024-11-14 14:28:30 +08:00
|
|
|
if (interrupt) return;
|
2024-11-12 15:17:00 +08:00
|
|
|
tcpdumpAll = adbUtil.startTcpdump(T.StrUtil.EMPTY);
|
|
|
|
|
if (0 != tcpdumpAll.exitCode()) {
|
|
|
|
|
T.FileUtil.appendString(String.format("ERROR: Start tcpdump all failed: exit code %s \n", tcpdumpAll.exitCode()), logFile, "UTF-8");
|
|
|
|
|
throw new APIException("tcpdump all error");
|
2024-10-17 17:10:45 +08:00
|
|
|
}
|
|
|
|
|
|
2024-11-12 15:17:00 +08:00
|
|
|
// exec playbook
|
2024-11-14 14:28:30 +08:00
|
|
|
if (interrupt) return;
|
2024-11-12 15:17:00 +08:00
|
|
|
AdbUtil.CommandResult airtestResult = adbUtil.execPlaybook(playbookDir.getPath(), logFile);
|
|
|
|
|
if (0 != airtestResult.exitCode()) {
|
|
|
|
|
T.FileUtil.appendString(String.format("ERROR: Exec playbook failed: exit code %s \n", airtestResult.exitCode()), logFile, "UTF-8");
|
|
|
|
|
throw new APIException("playbook exec error");
|
2024-10-17 17:10:45 +08:00
|
|
|
}
|
2024-10-31 17:02:52 +08:00
|
|
|
|
2024-11-12 15:17:00 +08:00
|
|
|
// stop package tcpdump
|
2024-11-14 14:28:30 +08:00
|
|
|
if (interrupt) return;
|
2024-11-12 15:17:00 +08:00
|
|
|
stopTcpdump(tcpdumpPackage, logFile, packageName);
|
2024-10-17 17:10:45 +08:00
|
|
|
|
2024-11-12 15:17:00 +08:00
|
|
|
// stop all tcpdump
|
2024-11-14 14:28:30 +08:00
|
|
|
if (interrupt) return;
|
2024-11-12 15:17:00 +08:00
|
|
|
stopTcpdump(tcpdumpAll, logFile, T.StrUtil.EMPTY);
|
2024-10-17 17:10:45 +08:00
|
|
|
|
|
|
|
|
resultMap = T.MapUtil.builder()
|
|
|
|
|
.put("status", "done")
|
|
|
|
|
.build();
|
2024-11-12 15:17:00 +08:00
|
|
|
T.FileUtil.writeString(T.JSONUtil.toJsonStr(resultMap), statusFile, "UTF-8");
|
2024-10-31 17:02:52 +08:00
|
|
|
} catch (Exception e) {
|
2024-10-17 17:10:45 +08:00
|
|
|
log.error(e);
|
|
|
|
|
Map resultMap = T.MapUtil.builder()
|
|
|
|
|
.put("status", "error")
|
|
|
|
|
.build();
|
2024-11-12 15:17:00 +08:00
|
|
|
T.FileUtil.writeString(T.JSONUtil.toJsonStr(resultMap), statusFile, "UTF-8");
|
2024-10-17 17:10:45 +08:00
|
|
|
} finally {
|
2024-11-14 14:28:30 +08:00
|
|
|
if (T.StrUtil.isNotEmpty(tcpdumpPackage.output())){
|
|
|
|
|
AdbUtil.CommandResult packageTcpdump = adbUtil.stopTcpdump(tcpdumpPackage.output());
|
|
|
|
|
adbUtil.execShellCommand(String.format("shell rm -rf %s", packageTcpdump.output()));
|
|
|
|
|
}
|
|
|
|
|
if (T.StrUtil.isNotEmpty(tcpdumpAll.output())){
|
|
|
|
|
AdbUtil.CommandResult allTcpdump = adbUtil.stopTcpdump(tcpdumpAll.output());
|
|
|
|
|
adbUtil.execShellCommand(String.format("shell rm -rf %s", allTcpdump.output()));
|
|
|
|
|
}
|
2024-11-18 14:50:34 +08:00
|
|
|
this.closeApp(ListUtil.empty(), packageName);
|
|
|
|
|
if (unInstall) {
|
|
|
|
|
adbUtil.uninstall(packageName);
|
|
|
|
|
}
|
|
|
|
|
T.FileUtil.appendString(String.format("Job execution ends"), logFile, "UTF-8");
|
2024-11-14 14:28:30 +08:00
|
|
|
ACTIVE_TASKS.remove(this);
|
2024-10-17 17:10:45 +08:00
|
|
|
}
|
|
|
|
|
}
|
2024-11-12 15:17:00 +08:00
|
|
|
|
2024-11-18 14:50:34 +08:00
|
|
|
private void closeApp(List<String> packageNameList, String packageName) {
|
|
|
|
|
if (CollUtil.isNotEmpty(packageNameList)){
|
|
|
|
|
for (String name : packageNameList) {
|
|
|
|
|
adbUtil.stopApp(name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
adbUtil.stopApp(packageName);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-14 14:28:30 +08:00
|
|
|
@Override
|
|
|
|
|
public void interrupt() {
|
|
|
|
|
super.interrupt();
|
|
|
|
|
this.interrupt = true;
|
|
|
|
|
adbUtil.setInterrupt(true);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-12 15:17:00 +08:00
|
|
|
private void stopTcpdump(AdbUtil.CommandResult tcpdump, File logFile, String packageName) {
|
|
|
|
|
// stop tcpdump
|
|
|
|
|
AdbUtil.CommandResult stopTcpdump = adbUtil.stopTcpdump(tcpdump.output());
|
|
|
|
|
if (0 != stopTcpdump.exitCode()) {
|
|
|
|
|
T.FileUtil.appendString(String.format("ERROR: Stop tcpdump failed: exit code %s \n", stopTcpdump.exitCode()), logFile, "UTF-8");
|
|
|
|
|
throw new APIException(stopTcpdump.output());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// pull pcap file
|
|
|
|
|
String filePath = stopTcpdump.output();
|
|
|
|
|
packageName = T.StrUtil.isEmpty(packageName) ? "all" : packageName;
|
|
|
|
|
File localPcapFile = T.FileUtil.file(Constant.TEMP_PATH, tid, String.format("%s-%s%s", tcpdump.output(), packageName, ".pcap"));
|
|
|
|
|
if (T.StrUtil.isEmpty(filePath)) {
|
|
|
|
|
throw new APIException(RCode.NOT_EXISTS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AdbUtil.CommandResult pull = adbUtil.pull(filePath, localPcapFile.getAbsolutePath());
|
|
|
|
|
if (0 != pull.exitCode()) {
|
|
|
|
|
T.FileUtil.appendString(String.format("ERROR: Pull pcap file failed: exit code %s \n", pull.exitCode()), logFile, "UTF-8");
|
|
|
|
|
throw new APIException(pull.output());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// delete android pcap
|
|
|
|
|
adbUtil.execShellCommand(String.format("shell rm -rf %s", filePath));
|
|
|
|
|
}
|
2024-10-17 17:10:45 +08:00
|
|
|
}
|
2024-08-22 09:22:52 +08:00
|
|
|
}
|