diff --git a/src/main/java/net/geedge/api/controller/APIController.java b/src/main/java/net/geedge/api/controller/APIController.java index 397b210..efa0045 100644 --- a/src/main/java/net/geedge/api/controller/APIController.java +++ b/src/main/java/net/geedge/api/controller/APIController.java @@ -1,6 +1,8 @@ package net.geedge.api.controller; import cn.hutool.core.codec.Base32Codec; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.log.Log; import jakarta.servlet.http.HttpServletResponse; import net.geedge.api.entity.EnvApiYml; import net.geedge.api.util.AdbUtil; @@ -18,6 +20,8 @@ import java.util.Map; @RequestMapping("/api/v1/env") public class APIController { + private final static Log log = Log.get(); + private final AdbUtil adbUtil; @Autowired @@ -246,4 +250,136 @@ public class APIController { return R.ok(); } + @PostMapping("/playbook") + public R execPlaybook(@RequestParam("files") MultipartFile[] files, @RequestParam("packageName") String packageName) throws IOException { + // save zip and apk + File appFile = null; + File playbookFile = null; + try { + for (MultipartFile file : files) { + if (T.FileUtil.extName(file.getOriginalFilename()).equals("zip")) { + playbookFile = T.FileUtil.file(Constant.TEMP_PATH, file.getOriginalFilename()); + file.transferTo(playbookFile); + T.ZipUtil.unzip(playbookFile, Constant.PLAYBOOK_AIR_PATH); + } else { + appFile = T.FileUtil.file(Constant.TEMP_PATH, file.getOriginalFilename()); + file.transferTo(appFile); + } + } + } finally { + T.FileUtil.del(playbookFile); + } + String tid = T.StrUtil.uuid(); + PlaybookRunnable playbookRunnable = new PlaybookRunnable(adbUtil, appFile, Constant.PLAYBOOK_AIR_PATH, tid, packageName); + ThreadUtil.execAsync(playbookRunnable); + return R.ok().putData("tid", tid); + } + + @GetMapping("/playbook/{id}") + public void getExecPlaybookResult( @PathVariable("id") String id, HttpServletResponse response) throws IOException { + if (T.StrUtil.isEmpty(id)) { + throw new APIException(RCode.BAD_REQUEST); + } + File tempFile = null; + try { + Map result = Constant.PLAYBOOK_RUN_RESULT.get(id); + if (result != null) { + if (T.MapUtil.getStr(result, "status").equals("done")) { + String artifact = T.MapUtil.getStr(result, "artifact"); + tempFile = T.FileUtil.file(artifact); + T.ResponseUtil.downloadFile(response, tempFile.getName(), T.FileUtil.readBytes(tempFile)); + } else { + response.getWriter().write(T.JSONUtil.toJsonStr(R.ok().putData(result))); + } + Constant.PLAYBOOK_RUN_RESULT.remove(id); + } else { + throw new APIException(RCode.BAD_REQUEST); + } + }finally { + T.FileUtil.del(tempFile); + } + } + + public class PlaybookRunnable extends Thread { + + private AdbUtil adbUtil; + private String tid; + private File apkFile; + private String packageName; + private File playbookDir; + + public PlaybookRunnable(AdbUtil adbUtil, File apkFile, File playbookDir, String tid, String packageName) { + this.adbUtil = adbUtil; + this.tid = tid; + this.apkFile = apkFile; + this.packageName = packageName; + this.playbookDir = playbookDir; + } + + @Override + public void run() { + try { + Map resultMap = T.MapUtil.builder() + .put("status", "running") + .build(); + Constant.PLAYBOOK_RUN_RESULT.put(tid, resultMap); + + // 1. install apk + AdbUtil.CommandResult install = adbUtil.install(apkFile.getAbsolutePath(), true, true); + if (0 != install.exitCode()) { + throw new APIException(install.output()); + } + + // 2. star tcpdump + AdbUtil.CommandResult startTcpdump = adbUtil.startTcpdump(packageName); + if (0 != startTcpdump.exitCode()) { + throw new APIException("exec tcpdump error"); + } + + // 3. exec playbook + AdbUtil.CommandResult execResult = adbUtil.execPlaybook(playbookDir.getPath()); + if (0 != execResult.exitCode()) { + // exec playbook error, stop tcpdump and delete pcap + AdbUtil.CommandResult stopTcpdump = adbUtil.stopTcpdump(startTcpdump.output()); + adbUtil.execShellCommand(String.format("shell rm -rf %s", stopTcpdump.output())); + throw new APIException("exec playbook error"); + } + + // 4. stop tcpdump + AdbUtil.CommandResult stopTcpdump = adbUtil.stopTcpdump(startTcpdump.output()); + if (0 != stopTcpdump.exitCode()) { + throw new APIException(stopTcpdump.output()); + } + + // 5. pull pcap file + String filePath = stopTcpdump.output(); + File localPcapFile = T.FileUtil.file(Constant.TEMP_PATH, startTcpdump.output() + ".pcap"); + if (T.StrUtil.isEmpty(filePath)) { + throw new APIException(RCode.NOT_EXISTS); + } + AdbUtil.CommandResult pull = adbUtil.pull(filePath, localPcapFile.getAbsolutePath()); + if (0 != pull.exitCode()) { + throw new APIException(pull.output()); + } + + // 6. delete android pcap + adbUtil.execShellCommand(String.format("shell rm -rf %s", filePath)); + + resultMap = T.MapUtil.builder() + .put("status", "done") + .put("artifact", localPcapFile.getAbsolutePath()) + .build(); + Constant.PLAYBOOK_RUN_RESULT.put(tid, resultMap); + }catch (Exception e) { + log.error(e); + Map resultMap = T.MapUtil.builder() + .put("status", "error") + .build(); + Constant.PLAYBOOK_RUN_RESULT.put(tid, resultMap); + } finally { + T.FileUtil.del(apkFile); + T.FileUtil.clean(playbookDir); + } + } + } } \ No newline at end of file diff --git a/src/main/java/net/geedge/api/util/AdbCommandBuilder.java b/src/main/java/net/geedge/api/util/AdbCommandBuilder.java index f77d70e..cd6490c 100644 --- a/src/main/java/net/geedge/api/util/AdbCommandBuilder.java +++ b/src/main/java/net/geedge/api/util/AdbCommandBuilder.java @@ -10,7 +10,7 @@ public class AdbCommandBuilder { private final String adbPath; private final List command; - private AdbCommandBuilder(String adbPath) { + public AdbCommandBuilder(String adbPath) { this.adbPath = adbPath; this.command = new LinkedList<>(); this.command.add(adbPath); @@ -199,6 +199,14 @@ public class AdbCommandBuilder { return this; } + public AdbCommandBuilder buildRunPlaybook(String path, String serial) { + this.command.add("run"); + this.command.add(path); + this.command.add("--device"); + this.command.add(T.StrUtil.concat(true,"Android://127.0.0.1:5037/", serial)); + return this; + } + public List build() { return this.command; } diff --git a/src/main/java/net/geedge/api/util/AdbUtil.java b/src/main/java/net/geedge/api/util/AdbUtil.java index 9c38683..7863486 100644 --- a/src/main/java/net/geedge/api/util/AdbUtil.java +++ b/src/main/java/net/geedge/api/util/AdbUtil.java @@ -848,4 +848,25 @@ public class AdbUtil { return process; } } + + public CommandResult execPlaybook(String playbookPath) { + log.info("[execPlaybook] [begin!]"); + Process process = CommandExec.execForProcess(new AdbCommandBuilder("airtest") + .buildRunPlaybook(playbookPath, this.serial) + .build()); + + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = executor.submit(() -> T.IoUtil.read(process.getInputStream(), T.CharsetUtil.CHARSET_UTF_8)); + try { + int exitCode = process.waitFor(); + String result = future.get(10, TimeUnit.SECONDS); + log.info("[execPlaybook] [result: {}]", result); + return new CommandResult(exitCode, result); + } catch (Exception e) { + process.destroyForcibly(); + throw new APIException(RCode.ERROR); + }finally { + executor.shutdown(); + } + } } \ No newline at end of file diff --git a/src/main/java/net/geedge/common/Constant.java b/src/main/java/net/geedge/common/Constant.java index a79b36b..88c78ac 100644 --- a/src/main/java/net/geedge/common/Constant.java +++ b/src/main/java/net/geedge/common/Constant.java @@ -1,6 +1,7 @@ package net.geedge.common; import java.io.File; +import java.util.Map; public class Constant { /** @@ -8,6 +9,10 @@ public class Constant { */ public static final String TEMP_PATH = System.getProperty("user.dir") + File.separator + "tmp"; + public static final File PLAYBOOK_AIR_PATH =T.FileUtil.file(T.WebPathUtil.getRootPath(), "tmp" , "playbook", "main.air"); + + public static final Map PLAYBOOK_RUN_RESULT = T.MapUtil.newConcurrentHashMap(); + static { File tempPath = T.FileUtil.file(TEMP_PATH); // 程序启动清空临时目录