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<ApkBuildTaskMapper, ApkBuildTask>
|
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 androidTarget, String repoAlias, String branch, String serverUrl) throws Exception {
|
// 检查是否有正在进行中的任务(状态0=等待中,1=打包中)
|
List<ApkBuildTask> pendingTasks = this.baseMapper.selectPendingTasks();
|
if (!pendingTasks.isEmpty()) {
|
ApkBuildTask runningTask = pendingTasks.get(0);
|
throw new RuntimeException("已有打包任务正在进行中(ID: " + runningTask.getId()
|
+ ", 项目: "
|
+ (runningTask.getProjectName() != null ? runningTask.getProjectName() : runningTask.getRepoAlias())
|
+ "),请等待完成后再创建新任务");
|
}
|
|
// 构建请求JSON
|
JSONObject requestBody = new JSONObject();
|
requestBody.put("build_type", buildType);
|
requestBody.put("android_target", androidTarget);
|
requestBody.put("repo_alias", repoAlias);
|
requestBody.put("branch", branch);
|
requestBody.put("server_url", serverUrl);
|
|
// 发送打包请求
|
Map<String, Object> 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<String, Object> 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<ApkBuildTask> 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<String> installApkToMultipleDevices(Long id, List<String> deviceIps) {
|
List<String> 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<ApkBuildTask> getPendingTasks() {
|
return this.baseMapper.selectPendingTasks();
|
}
|
|
/**
|
* 执行ADB命令
|
*/
|
private String executeAdbCommand(String... args) throws Exception {
|
List<String> 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;
|
}
|
}
|
}
|