package com.zy.asrs.service.impl; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.service.impl.ServiceImpl; import com.zy.asrs.entity.ApkBuildTask; import com.zy.asrs.mapper.ApkBuildTaskMapper; import com.zy.asrs.service.ApkBuildTaskService; import com.zy.common.utils.HttpHandler; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.concurrent.TimeUnit; /** * APK打包任务Service实现类 */ @Service public class ApkBuildTaskServiceImpl extends ServiceImpl implements ApkBuildTaskService { private static final Logger log = LoggerFactory.getLogger(ApkBuildTaskServiceImpl.class); @Value("${apk-build.build-server-url}") private String buildServerUrl; @Value("${apk-build.download-server-url}") private String downloadServerUrl; @Value("${apk-build.x-token}") private String xToken; @Value("${apk-build.apk-download-path}") private String apkDownloadPath; @Value("${adb.path}") private String adbPath; @Override public ApkBuildTask triggerBuild(String buildType, String repoAlias, String branch) throws Exception { // 构建请求JSON JSONObject requestBody = new JSONObject(); requestBody.put("build_type", buildType); requestBody.put("repo_alias", repoAlias); requestBody.put("branch", branch); // 发送打包请求 Map headers = new HashMap<>(); headers.put("X-Token", xToken); String response = new HttpHandler.Builder() .setUri(buildServerUrl) .setPath("/build") .setHeaders(headers) .setJson(requestBody.toJSONString()) .build() .doPost(); log.info("触发打包响应: {}", response); if (response == null) { throw new RuntimeException("打包服务无响应"); } JSONObject result = JSON.parseObject(response); String taskId = result.getString("task_id"); String status = result.getString("status"); Integer queueSize = result.getInteger("queue_size"); if (taskId == null || taskId.isEmpty()) { throw new RuntimeException("打包服务返回的task_id为空"); } // 创建本地任务记录 ApkBuildTask task = new ApkBuildTask(); task.setTaskId(taskId); task.setBuildType(buildType); task.setRepoAlias(repoAlias); task.setBranch(branch); task.setStatus(convertStatus(status)); task.setQueueSize(queueSize); task.setCreatedAt(new Date()); this.insert(task); return task; } @Override public ApkBuildTask refreshStatus(Long id) throws Exception { ApkBuildTask task = this.selectById(id); if (task == null) { throw new RuntimeException("任务不存在"); } // 如果任务已完成或失败,无需刷新 if (task.getStatus() == 2 || task.getStatus() == 3) { return task; } // 查询远程状态 Map headers = new HashMap<>(); headers.put("X-Token", xToken); String response = new HttpHandler.Builder() .setUri(buildServerUrl) .setPath("/build/" + task.getTaskId()) .setHeaders(headers) .build() .doGet(); log.info("查询状态响应: {}", response); if (response == null) { throw new RuntimeException("查询状态服务无响应"); } JSONObject result = JSON.parseObject(response); String status = result.getString("status"); String artifactPath = result.getString("artifact_path"); String projectName = result.getString("project_name"); String error = result.getString("error"); String meta = result.getString("meta"); Long createdAtTimestamp = result.getLong("created_at"); Long startedAtTimestamp = result.getLong("started_at"); Long finishedAtTimestamp = result.getLong("finished_at"); // 更新任务信息 task.setStatus(convertStatus(status)); task.setArtifactPath(artifactPath); task.setProjectName(projectName); task.setError(error); task.setMeta(meta); if (createdAtTimestamp != null) { task.setCreatedAt(new Date((long) (createdAtTimestamp * 1000))); } if (startedAtTimestamp != null) { task.setStartedAt(new Date((long) (startedAtTimestamp * 1000))); } if (finishedAtTimestamp != null) { task.setFinishedAt(new Date((long) (finishedAtTimestamp * 1000))); } // 如果打包成功,自动下载APK if ("SUCCESS".equals(status) && artifactPath != null && task.getApkPath() == null) { try { String localPath = downloadApkInternal(task); task.setApkPath(localPath); } catch (Exception e) { log.error("自动下载APK失败: {}", e.getMessage()); } } this.updateById(task); return task; } @Override public int refreshAllPendingTasks() { List pendingTasks = this.baseMapper.selectPendingTasks(); int count = 0; for (ApkBuildTask task : pendingTasks) { try { refreshStatus(task.getId()); count++; } catch (Exception e) { log.error("刷新任务 {} 状态失败: {}", task.getId(), e.getMessage()); } } return count; } @Override public String downloadApk(Long id) throws Exception { ApkBuildTask task = this.selectById(id); if (task == null) { throw new RuntimeException("任务不存在"); } if (task.getStatus() != 2) { throw new RuntimeException("任务尚未完成,无法下载"); } if (task.getApkPath() != null && new File(task.getApkPath()).exists()) { return task.getApkPath(); } String localPath = downloadApkInternal(task); task.setApkPath(localPath); this.updateById(task); return localPath; } /** * 内部下载APK方法 */ private String downloadApkInternal(ApkBuildTask task) throws Exception { if (task.getTaskId() == null) { throw new RuntimeException("任务ID为空"); } String downloadUrl = downloadServerUrl + "/build/" + task.getTaskId() + "/download"; log.info("开始下载APK: {}", downloadUrl); // 创建下载目录 Path downloadDir = Paths.get(apkDownloadPath); if (!Files.exists(downloadDir)) { Files.createDirectories(downloadDir); } // 生成本地文件名 String fileName = task.getProjectName() != null ? task.getProjectName() + ".apk" : task.getTaskId() + ".apk"; Path localFile = downloadDir.resolve(fileName); // 下载文件 OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(60, TimeUnit.SECONDS) .readTimeout(300, TimeUnit.SECONDS) .build(); Request request = new Request.Builder() .url(downloadUrl) .addHeader("X-Token", xToken) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) { throw new RuntimeException("下载失败,HTTP状态码: " + response.code()); } try (InputStream is = response.body().byteStream(); OutputStream os = new FileOutputStream(localFile.toFile())) { byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } } } log.info("APK下载完成: {}", localFile.toString()); return localFile.toString(); } @Override public String installApk(Long id, String deviceIp) throws Exception { ApkBuildTask task = this.selectById(id); if (task == null) { throw new RuntimeException("任务不存在"); } if (task.getApkPath() == null || !new File(task.getApkPath()).exists()) { throw new RuntimeException("APK文件不存在,请先下载"); } StringBuilder result = new StringBuilder(); // 连接设备 String connectResult = executeAdbCommand("connect", deviceIp + ":5555"); result.append("连接设备: ").append(connectResult).append("\n"); // 等待设备连接稳定 Thread.sleep(1000); // 安装APK String installResult = executeAdbCommand("-s", deviceIp + ":5555", "install", "-r", task.getApkPath()); result.append("安装结果: ").append(installResult); log.info("ADB安装结果: {}", result.toString()); return result.toString(); } @Override public List installApkToMultipleDevices(Long id, List deviceIps) { List results = new ArrayList<>(); for (String deviceIp : deviceIps) { try { String result = installApk(id, deviceIp); results.add(deviceIp + ": " + result); } catch (Exception e) { results.add(deviceIp + ": 安装失败 - " + e.getMessage()); } } return results; } @Override public List getPendingTasks() { return this.baseMapper.selectPendingTasks(); } /** * 执行ADB命令 */ private String executeAdbCommand(String... args) throws Exception { List command = new ArrayList<>(); command.add(adbPath); command.addAll(Arrays.asList(args)); log.info("执行ADB命令: {}", String.join(" ", command)); ProcessBuilder pb = new ProcessBuilder(command); pb.redirectErrorStream(true); Process process = pb.start(); StringBuilder output = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { output.append(line).append("\n"); } } int exitCode = process.waitFor(); String result = output.toString().trim(); if (exitCode != 0 && !result.contains("Success")) { log.warn("ADB命令执行返回码: {}, 输出: {}", exitCode, result); } return result; } /** * 转换状态字符串为数字 */ private Short convertStatus(String status) { if (status == null) { return 0; } switch (status.toUpperCase()) { case "PENDING": return 0; case "BUILDING": case "RUNNING": return 1; case "SUCCESS": return 2; case "FAILED": case "ERROR": return 3; default: return 0; } } }