package com.mir4updater.backend.controller.apk; import com.fasterxml.jackson.databind.ObjectMapper; import com.mir4updater.backend.config.FilePath; import com.mir4updater.backend.entity.ApkInfo; import com.mir4updater.backend.entity.XapkManifest; import com.mir4updater.backend.result.Result; import com.mir4updater.backend.service.ApkInfoService; import com.mir4updater.backend.utils.ApkUtils; import com.mir4updater.backend.utils.HashUtils; import com.mir4updater.backend.utils.PatchUtils; import net.dongliu.apk.parser.ApkFile; import net.dongliu.apk.parser.bean.ApkMeta; import net.dongliu.apk.parser.bean.UseFeature; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tomcat.util.http.fileupload.FileUploadException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @RestController public class CreateDiffPatchFileController { public static Logger logger = LogManager.getLogger(CreateDiffPatchFileController.class); @Autowired private ApkInfoService apkInfoService; @PostMapping("/android/create_patch") public Result uploadApk(@RequestParam("old_id") int oldId, @RequestParam("new_id") int newId) throws Exception { String projectPath = System.getProperty("user.dir"); String patchPath = projectPath + File.separator + FilePath.UPLOAD_PATH + File.separator + FilePath.PATCH_PATH + File.separator; File patchFileDir = new File(patchPath); if (!patchFileDir.exists()) { patchFileDir.mkdirs(); } ApkInfo oldApkInfo = apkInfoService.getApkInfo(oldId); ApkInfo newApkInfo = apkInfoService.getApkInfo(newId); if (oldApkInfo == null || newApkInfo == null) { return Result.notFound(); } if (!Objects.equals(oldApkInfo.getPackageName(), newApkInfo.getPackageName())) { return Result.error("包名不一致"); } if (oldApkInfo.getVersionCode() >= newApkInfo.getVersionCode()) { return Result.error("旧版本号不能大于等于新版本号"); } File patchFile = new File(patchPath + oldApkInfo.getPackageName() + "_" + oldApkInfo.getVersionCode() + "_" + newApkInfo.getVersionCode() + ".patch"); long startTime = System.currentTimeMillis(); logger.info("started at " + startTime); PatchUtils.BsdiffResult bsdiffResult = PatchUtils.createDiffPatch(oldApkInfo.getFilePath(), newApkInfo.getFilePath(), patchFile.getPath()); long endTime = System.currentTimeMillis() - startTime; int exitCode = bsdiffResult.getExitCode(); String result = bsdiffResult.getResult(); if (exitCode == 0) { String md5 = HashUtils.getFileMD5(patchFile); logger.info("patchFile md5 = " + md5 + " 用时:" + endTime + "ms"); return Result.success(md5); } else { return Result.error(result); } } /** * 通过上传文件查询最新版本并创建最新补丁文件 * * @param multipartFile * @return * @throws Exception */ @PostMapping("/android/create_latest_patch") public Result createLatestPatch(@RequestParam("file") MultipartFile multipartFile) throws Exception { if (multipartFile != null && !multipartFile.isEmpty()) { String projectPath = System.getProperty("user.dir"); logger.info("当前项目路径为:" + projectPath); String tempPath = projectPath + File.separator + FilePath.UPLOAD_PATH + File.separator + FilePath.TEMP_PATH + File.separator; File tempFileDir = new File(tempPath); if (!tempFileDir.exists()) { tempFileDir.mkdirs(); } //1.接收上传的应用,写入到硬盘 String originalFilename = multipartFile.getOriginalFilename(); File tempFile = new File(tempPath + File.separator + originalFilename); logger.info("tempFile path = " + tempFile.getAbsolutePath()); try { multipartFile.transferTo(tempFile); } catch (FileUploadException e) { logger.error(e.getMessage()); } if (!ApkUtils.isAPK(tempFile) && !ApkUtils.isXAPK(tempFile)) { return Result.error().setMessage("请上传apk或xapk文件"); } String md5 = HashUtils.getFileMD5(tempFile); logger.info("tempFile md5 = " + md5); String sha1 = HashUtils.calculateSHA1(tempFile); logger.info("tempFile sha1 = " + sha1); String sha256 = HashUtils.calculateSHA256(tempFile); logger.info("tempFile sha256 = " + sha256); if (ApkUtils.isAPK(tempFile)) { logger.info("tempFile type is apk"); ApkFile apkFile = new ApkFile(tempFile); ApkMeta apkMeta = apkFile.getApkMeta(); apkFile.close(); logger.info(apkMeta.getLabel()); logger.info(apkMeta.getPackageName()); logger.info(apkMeta.getVersionCode()); logger.info(apkMeta.getVersionName()); for (UseFeature feature : apkMeta.getUsesFeatures()) { logger.info(feature.getName()); } String packageName = apkMeta.getPackageName(); long versionCode = apkMeta.getVersionCode(); List apkInfoList = apkInfoService.getApkInfo(packageName); Optional apkInfoOptional = apkInfoList.stream().max(new Comparator() { @Override public int compare(ApkInfo o1, ApkInfo o2) { return Long.compare(o1.getVersionCode(), o2.getVersionCode()); } }); if (apkInfoOptional.isPresent()) { ApkInfo apkInfo = apkInfoOptional.get(); logger.info("latest apkinfo = " + apkInfo); if (versionCode >= apkInfo.getVersionCode()) { return Result.success().setMessage(packageName + " 已是最新版本"); } else { String apkPath = apkInfo.getFilePath(); String patchPath = projectPath + File.separator + FilePath.UPLOAD_PATH + File.separator + FilePath.PATCH_PATH + File.separator; File patchFile = new File(patchPath + packageName + "_" + versionCode + "_" + apkInfo.getVersionCode() + ".patch"); long startTime = System.currentTimeMillis(); logger.info("started at " + startTime); PatchUtils.BsdiffResult bsdiffResult = PatchUtils.createDiffPatch(tempFile.getAbsolutePath(), apkPath, patchFile.getPath()); long endTime = System.currentTimeMillis() - startTime; int exitCode = bsdiffResult.getExitCode(); String result = bsdiffResult.getResult(); if (exitCode == 0) { String patchMd5 = HashUtils.getFileMD5(patchFile); logger.info("patchFile md5 = " + patchMd5); logger.info("tempFile delete = " + tempFile.delete()); Map map = new HashMap<>(); map.put("patchMd5", patchMd5); map.put("path", patchFile.getAbsolutePath()); map.put("time", endTime + "ms"); return Result.mapResult(map).setSuccess(true).setCode(Result.CODE_SUCCESS).setMessage("创建成功"); } else { logger.info("tempFile delete = " + tempFile.delete()); return Result.error(result); } } } else { logger.info("tempFile delete = " + tempFile.delete()); return Result.error().setMessage(packageName + " 没有上传其他版本"); } } else if (ApkUtils.isXAPK(tempFile)) { logger.info("tempFile type is xapk"); try (ZipFile zipFile = new ZipFile(tempFile)) { // 遍历ZIP文件中的所有条目 zipFile.stream().forEach(zipEntry -> { logger.info(zipEntry.getName()); }); Optional manifestOptional = zipFile.stream().filter(zipEntry -> "manifest.json".equals(zipEntry.getName())).findFirst(); if (manifestOptional.isPresent()) { ZipEntry manifestZipEntry = manifestOptional.get(); try { InputStream manifestInputStream = zipFile.getInputStream(manifestZipEntry); // 读取文件内容 String content = new String(manifestInputStream.readAllBytes(), StandardCharsets.UTF_8); // 创建ObjectMapper实例 ObjectMapper objectMapper = new ObjectMapper(); // 将Java对象转换为JSON字符串 String jsonString = objectMapper.writeValueAsString(content); XapkManifest xapkManifest = objectMapper.readValue(content, XapkManifest.class); // logger.info(xapkManifest); logger.info(xapkManifest.getName()); logger.info(xapkManifest.getPackage_name()); logger.info(xapkManifest.getVersion_code()); logger.info(xapkManifest.getVersion_name()); } catch (IOException e) { e.printStackTrace(); System.err.println("读取文件失败: " + manifestZipEntry.getName()); return Result.error().setMessage("读取文件失败"); } } else { return Result.error().setMessage("读取xapk配置失败"); } } catch (IOException e) { e.printStackTrace(); System.err.println("打开ZIP文件失败: " + e.getMessage()); return Result.error().setMessage("打开ZIP文件失败"); } } } else { return Result.error().setMessage("文件为空"); } return Result.error().setMessage("未知错误"); } }