diff --git a/DockerFile b/DockerFile new file mode 100644 index 0000000..92da72b --- /dev/null +++ b/DockerFile @@ -0,0 +1,8 @@ +FROM eclipse-temurin:17-jdk-jammy +MAINTAINER TongTongStudio +RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak +VOLUME /tmp +ADD bsdiff . +ADD target/*.jar app.jar +EXPOSE 65534 +ENTRYPOINT ["java", "-jar", "/app.jar"] \ No newline at end of file diff --git a/src/main/java/com/mir4updater/backend/config/FilePath.java b/src/main/java/com/mir4updater/backend/config/FilePath.java new file mode 100644 index 0000000..0f0f4f0 --- /dev/null +++ b/src/main/java/com/mir4updater/backend/config/FilePath.java @@ -0,0 +1,10 @@ +package com.mir4updater.backend.config; + +public class FilePath { + + public static final String UPLOAD_PATH = "uploadApk"; + public static final String APK_FILE_PATH = "file"; + public static final String ICON_PATH = "icon"; + public static final String TEMP_PATH = "tmp"; + public static final String PATCH_PATH = "patch"; +} diff --git a/src/main/java/com/mir4updater/backend/controller/apk/CheckPatchController.java b/src/main/java/com/mir4updater/backend/controller/apk/CheckPatchController.java new file mode 100644 index 0000000..349e586 --- /dev/null +++ b/src/main/java/com/mir4updater/backend/controller/apk/CheckPatchController.java @@ -0,0 +1,13 @@ +package com.mir4updater.backend.controller.apk; + +import org.apache.logging.log4j.LogManager; +import org.springframework.web.bind.annotation.RestController; + +import org.apache.logging.log4j.Logger; + +@RestController +public class CheckPatchController { + public static Logger logger = LogManager.getLogger(CheckPatchController.class); + + +} diff --git a/src/main/java/com/mir4updater/backend/controller/apk/CreateDiffPatchFile.java b/src/main/java/com/mir4updater/backend/controller/apk/CreateDiffPatchFile.java deleted file mode 100644 index c724d70..0000000 --- a/src/main/java/com/mir4updater/backend/controller/apk/CreateDiffPatchFile.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.mir4updater.backend.controller.apk; - -import com.mir4updater.backend.entity.ApkInfo; -import com.mir4updater.backend.result.Result; -import com.mir4updater.backend.service.ApkInfoService; -import com.mir4updater.backend.utils.HashUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -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 java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -@RestController -public class CreateDiffPatchFile { - public static Logger logger = LogManager.getLogger(CreateDiffPatchFile.class); - - private static final String UPLOAD_PATH = "uploadApk"; - - public static final String PATCH_PATH = "patch"; - @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 + UPLOAD_PATH + File.separator + 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"); - - // 根据操作系统选择命令 - String os = System.getProperty("os.name").toLowerCase(); - String command; - if (os.contains("win")) { - command = "bsdiff" + File.separator + "win" + File.separator + "bsdiff.exe"; - } else if (os.contains("linux") ) { - command = "bsdiff" + File.separator + "linux" + File.separator + "bsdiff"; - }else if ( os.contains("mac")) { - command = "bsdiff" + File.separator + "mac" + File.separator + "bsdiff"; - } else { - throw new UnsupportedOperationException("Unsupported OS"); - } - File file = new File(command); - logger.info("bsdiff file exists = " + file.exists()); - - // 将 command 和 arg 合并成一个数组,command 作为第一个参数 - List cmdArgs = new ArrayList<>(); - cmdArgs.add(command); // 第一个参数是命令本身 - cmdArgs.add(oldApkInfo.getFilePath());// 后续参数 - cmdArgs.add(newApkInfo.getFilePath()); - cmdArgs.add(patchFile.getAbsolutePath()); - - logger.info("bsdiff args = " + cmdArgs); - - ProcessBuilder pb = new ProcessBuilder(cmdArgs); // 传入合并后的参数列表 - pb.redirectErrorStream(true); // 合并错误流和输入流 - Process process = pb.start(); - - int exitCode = process.waitFor(); - logger.info("退出码: " + exitCode); - - // 读取输出 - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - String result = reader.lines().collect(Collectors.joining("\n")); - if (exitCode == 0) { - String md5 = HashUtils.getFileMD5(patchFile); - logger.info("patchFile md5 = " + md5); - return Result.success(md5); - } else { - return Result.error(result); - } - } - -} diff --git a/src/main/java/com/mir4updater/backend/controller/apk/CreateDiffPatchFileController.java b/src/main/java/com/mir4updater/backend/controller/apk/CreateDiffPatchFileController.java new file mode 100644 index 0000000..8bfff87 --- /dev/null +++ b/src/main/java/com/mir4updater/backend/controller/apk/CreateDiffPatchFileController.java @@ -0,0 +1,221 @@ +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("未知错误"); + } +} diff --git a/src/main/java/com/mir4updater/backend/controller/apk/UploadApkController.java b/src/main/java/com/mir4updater/backend/controller/apk/UploadApkController.java index 1f2e755..809c235 100644 --- a/src/main/java/com/mir4updater/backend/controller/apk/UploadApkController.java +++ b/src/main/java/com/mir4updater/backend/controller/apk/UploadApkController.java @@ -1,10 +1,12 @@ 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.FileUtils; import com.mir4updater.backend.utils.HashUtils; import net.dongliu.apk.parser.ApkFile; @@ -33,10 +35,7 @@ import java.util.zip.ZipFile; @RestController public class UploadApkController { - private static final String UPLOAD_PATH = "uploadApk"; - private static final String APK_FILE_PATH = "file"; - private static final String ICON_PATH = "icon"; - private static final String TEMP_PATH = "tmp"; + public static Logger logger = LogManager.getLogger(UploadApkController.class); @@ -50,9 +49,9 @@ public class UploadApkController { String projectPath = System.getProperty("user.dir"); logger.info("当前项目路径为:" + projectPath); - String apkPath = projectPath + File.separator + UPLOAD_PATH + File.separator + APK_FILE_PATH; - String iconPath = projectPath + File.separator + UPLOAD_PATH + File.separator + ICON_PATH + File.separator; - String tempPath = projectPath + File.separator + UPLOAD_PATH + File.separator + TEMP_PATH + File.separator; + String apkPath = projectPath + File.separator + FilePath.UPLOAD_PATH + File.separator + FilePath.APK_FILE_PATH; + String iconPath = projectPath + File.separator + FilePath.UPLOAD_PATH + File.separator + FilePath.ICON_PATH + File.separator; + String tempPath = projectPath + File.separator + FilePath.UPLOAD_PATH + File.separator + FilePath.TEMP_PATH + File.separator; File apkFileDir = new File(apkPath); if (!apkFileDir.exists()) { @@ -78,7 +77,7 @@ public class UploadApkController { logger.error(e.getMessage()); } - if (!isAPK(file) && !isXAPK(file)) { + if (!ApkUtils.isAPK(file) && !ApkUtils.isXAPK(file)) { return Result.error().setMessage("请上传apk或xapk文件"); } String md5 = HashUtils.getFileMD5(file); @@ -90,7 +89,7 @@ public class UploadApkController { File iconFile = new File(iconPath + md5 + ".png"); - if (isAPK(file)) { + if (ApkUtils.isAPK(file)) { logger.info("file type is apk"); //调用工具类解析apk //analysisAPK(apkFileDir.getAbsolutePath(), file.getAbsolutePath()); @@ -135,7 +134,7 @@ public class UploadApkController { } return Result.success().setMessage("上传成功"); - } else if (isXAPK(file)) { + } else if (ApkUtils.isXAPK(file)) { logger.info("file type is xapk"); try (ZipFile zipFile = new ZipFile(file)) { // 遍历ZIP文件中的所有条目 @@ -210,54 +209,6 @@ public class UploadApkController { return Result.error().setMessage("未知错误"); } - // 判断是否为APK - public static boolean isAPK(File file) throws IOException { - // 检查扩展名是否为.apk - if (file.getName().toLowerCase().endsWith(".apk")) { - // 进一步验证是否为有效的APK文件 - ZipFile zipFile = new ZipFile(file); - boolean apkFile = zipFile.getEntry("AndroidManifest.xml") != null; - zipFile.close(); - return apkFile; - } - return false; - } - - // 判断是否为XAPK - public static boolean isXAPK(File file) { - // 检查扩展名是否为.xapk - if (file.getName().toLowerCase().endsWith(".xapk")) { - return true; // 扩展名为.xapk通常直接视为XAPK - } - - // 若扩展名不明确,检查内容结构 - try (ZipFile zipFile = new ZipFile(file)) { - boolean hasJson = zipFile.getEntry("manifest.json") != null; - - boolean hasAPK = false; -// boolean hasOBB = false; - - Enumeration entries = zipFile.entries(); - - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - String name = entry.getName(); - // 检查是否包含APK文件 - if (name.endsWith(".apk")) { - hasAPK = true; - } - // 检查是否包含OBB目录 -// if (name.startsWith("Android/obb/")) { -// hasOBB = true; -// } - } - zipFile.close(); - // XAPK通常包含至少一个APK或OBB数据 - return hasAPK || hasJson; - } catch (IOException e) { - return false; - } - } private void analysisAPK(String dirPath, String filePath) throws Exception { diff --git a/src/main/java/com/mir4updater/backend/entity/ApkInfo.java b/src/main/java/com/mir4updater/backend/entity/ApkInfo.java index f16bec8..2c06e69 100644 --- a/src/main/java/com/mir4updater/backend/entity/ApkInfo.java +++ b/src/main/java/com/mir4updater/backend/entity/ApkInfo.java @@ -3,6 +3,8 @@ package com.mir4updater.backend.entity; import jakarta.persistence.*; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; import java.io.Serializable; @@ -10,8 +12,9 @@ import java.io.Serializable; @Entity @Table(name = "apk_info") @EqualsAndHashCode - public class ApkInfo implements Serializable { + public ApkInfo() { + } @Embedded private ApkInfoPK id; // 复合主键 @@ -51,86 +54,6 @@ public class ApkInfo implements Serializable { @Column(name = "xapk_version", length = 11) int xapk_version; - public ApkInfo() { - } - public String getAppName() { - return appName; - } - public void setAppName(String appName) { - this.appName = appName; - } - - public String getPackageName() { - return packageName; - } - - public void setPackageName(String packageName) { - this.packageName = packageName; - } - - public long getVersionCode() { - return versionCode; - } - - public void setVersionCode(long versionCode) { - this.versionCode = versionCode; - } - - public String getVersionName() { - return versionName; - } - - public void setVersionName(String versionName) { - this.versionName = versionName; - } - - public String getSha1() { - return sha1; - } - - public void setSha1(String sha1) { - this.sha1 = sha1; - } - - public String getFilePath() { - return filePath; - } - - public void setFilePath(String filePath) { - this.filePath = filePath; - } - - public String getIconPath() { - return iconPath; - } - - public void setIconPath(String iconPath) { - this.iconPath = iconPath; - } - - public long getSize() { - return size; - } - - public void setSize(long size) { - this.size = size; - } - - public boolean isXapk() { - return xapk; - } - - public void setXapk(boolean xapk) { - this.xapk = xapk; - } - - public int getXapk_version() { - return xapk_version; - } - - public void setXapk_version(int xapk_version) { - this.xapk_version = xapk_version; - } } diff --git a/src/main/java/com/mir4updater/backend/entity/PatchFileInfo.java b/src/main/java/com/mir4updater/backend/entity/PatchFileInfo.java new file mode 100644 index 0000000..b486579 --- /dev/null +++ b/src/main/java/com/mir4updater/backend/entity/PatchFileInfo.java @@ -0,0 +1,63 @@ +package com.mir4updater.backend.entity; + +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +@Data +@Entity +@Table(name = "patch_file_info") +@EqualsAndHashCode +public class PatchFileInfo implements Serializable { + @Id + @Column(name = "patch_id", nullable = false, length = 20) + @GeneratedValue(strategy = GenerationType.IDENTITY) // 主键自增 + int appId; + + @Column(name = "package_name", nullable = false) + String packageName; + + @Column(name = "old_version", nullable = false) + long old_version; + + @Column(name = "old_md5", nullable = false) + String old_md5; + + @Column(name = "old_sha1", nullable = false) + String old_sha1; + + @Column(name = "old_sha256", nullable = false) + String old_sha256; + + @Column(name = "new_version", nullable = false) + long new_version; + + @Column(name = "new_md5", nullable = false) + String new_md5; + + @Column(name = "new_sha1", nullable = false) + String new_sha1; + + @Column(name = "new_sha256", nullable = false) + String new_sha256; + + @Column(name = "patch_file_path", nullable = false) + String patch_file_path; + + @Column(name = "patch_file_md5", nullable = false) + String patch_file_md5; + + @Column(name = "patch_file_sha1", nullable = false) + String patch_file_sha1; + + @Column(name = "patch_file_sha256", nullable = false) + String patch_file_sha256; + + @Column(name = "new_file_name", nullable = false) + String new_file_name; + + @Column(name = "is_xapk", columnDefinition = "bit(1) default 0") + boolean xapk = false; +} diff --git a/src/main/java/com/mir4updater/backend/repository/PatchInfoRepository.java b/src/main/java/com/mir4updater/backend/repository/PatchInfoRepository.java new file mode 100644 index 0000000..cf015fd --- /dev/null +++ b/src/main/java/com/mir4updater/backend/repository/PatchInfoRepository.java @@ -0,0 +1,7 @@ +package com.mir4updater.backend.repository; + +import com.mir4updater.backend.entity.ApkInfo; +import org.springframework.data.jpa.repository.JpaRepository; + +//public class PatchInfoRepository extends JpaRepository { +//} diff --git a/src/main/java/com/mir4updater/backend/service/DiffPathService.java b/src/main/java/com/mir4updater/backend/service/DiffPathService.java new file mode 100644 index 0000000..3038ce7 --- /dev/null +++ b/src/main/java/com/mir4updater/backend/service/DiffPathService.java @@ -0,0 +1,8 @@ +package com.mir4updater.backend.service; + +import org.springframework.stereotype.Service; + +@Service +public class DiffPathService { + +} diff --git a/src/main/java/com/mir4updater/backend/utils/ApkUtils.java b/src/main/java/com/mir4updater/backend/utils/ApkUtils.java new file mode 100644 index 0000000..647b802 --- /dev/null +++ b/src/main/java/com/mir4updater/backend/utils/ApkUtils.java @@ -0,0 +1,60 @@ +package com.mir4updater.backend.utils; + +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class ApkUtils { + + // 判断是否为APK + public static boolean isAPK(File file) throws IOException { + // 检查扩展名是否为.apk + if (file.getName().toLowerCase().endsWith(".apk")) { + // 进一步验证是否为有效的APK文件 + ZipFile zipFile = new ZipFile(file); + boolean apkFile = zipFile.getEntry("AndroidManifest.xml") != null; + zipFile.close(); + return apkFile; + } + return false; + } + + // 判断是否为XAPK + public static boolean isXAPK(File file) { + // 检查扩展名是否为.xapk + if (file.getName().toLowerCase().endsWith(".xapk")) { + return true; // 扩展名为.xapk通常直接视为XAPK + } + + // 若扩展名不明确,检查内容结构 + try (ZipFile zipFile = new ZipFile(file)) { + boolean hasJson = zipFile.getEntry("manifest.json") != null; + + boolean hasAPK = false; +// boolean hasOBB = false; + + Enumeration entries = zipFile.entries(); + + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + // 检查是否包含APK文件 + if (name.endsWith(".apk")) { + hasAPK = true; + } + // 检查是否包含OBB目录 +// if (name.startsWith("Android/obb/")) { +// hasOBB = true; +// } + } + zipFile.close(); + // XAPK通常包含至少一个APK或OBB数据 + return hasAPK || hasJson; + } catch (IOException e) { + return false; + } + } + +} diff --git a/src/main/java/com/mir4updater/backend/utils/PatchUtils.java b/src/main/java/com/mir4updater/backend/utils/PatchUtils.java new file mode 100644 index 0000000..c5b24a7 --- /dev/null +++ b/src/main/java/com/mir4updater/backend/utils/PatchUtils.java @@ -0,0 +1,82 @@ +package com.mir4updater.backend.utils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class PatchUtils { + public static Logger logger = LogManager.getLogger(PatchUtils.class); + + public static BsdiffResult createDiffPatch(String oldApkPath, String newApkPath, String patchFilePath) throws Exception { + // 根据操作系统选择命令 + String os = System.getProperty("os.name").toLowerCase(); + String command; + if (os.contains("win")) { + command = "bsdiff" + File.separator + "win" + File.separator + "bsdiff.exe"; + } else if (os.contains("linux")) { + command = "bsdiff" + File.separator + "linux" + File.separator + "bsdiff"; + } else if (os.contains("mac")) { + command = "bsdiff" + File.separator + "mac" + File.separator + "bsdiff"; + } else { + throw new UnsupportedOperationException("Unsupported OS"); + } + File file = new File(command); + logger.info("bsdiff file exists = " + file.exists()); + + // 将 command 和 arg 合并成一个数组,command 作为第一个参数 + List cmdArgs = new ArrayList<>(); + cmdArgs.add(command); // 第一个参数是命令本身 + cmdArgs.add(oldApkPath);// 后续参数 + cmdArgs.add(newApkPath); + cmdArgs.add(patchFilePath); + + logger.info("bsdiff args = " + cmdArgs); + + ProcessBuilder pb = new ProcessBuilder(cmdArgs); // 传入合并后的参数列表 + pb.redirectErrorStream(true); // 合并错误流和输入流 + Process process = pb.start(); + + int exitCode = process.waitFor(); + logger.info("退出码: " + exitCode); + + // 读取输出 + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String result = reader.lines().collect(Collectors.joining("\n")); + BsdiffResult bsdiffResult = new BsdiffResult(exitCode, result); + + return bsdiffResult; + } + + public static class BsdiffResult implements Serializable { + int exitCode; + String result; + + public BsdiffResult(int exitCode, String result) { + this.exitCode = exitCode; + this.result = result; + } + + public int getExitCode() { + return exitCode; + } + + public void setExitCode(int exitCode) { + this.exitCode = exitCode; + } + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 78e8b86..482aa3a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,36 +1,37 @@ spring.application.name=Mir4Updater server.port=65534 -## 数据连接信息 + +## \u6570\u636E\u8FDE\u63A5\u4FE1\u606F spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3305/spring_boot?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true spring.datasource.username=tt spring.datasource.password=fanhuitong -# Hibernate配置 +# Hibernate\u914D\u7F6E spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect #spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect -# 启用 SQL 日志 +# \u542F\u7528 SQL \u65E5\u5FD7 spring.jpa.show-sql=true -# 格式化 SQL +# \u683C\u5F0F\u5316 SQL #spring.jpa.properties.hibernate.format_sql=true -# 显示绑定参数的具体值(关键配置) +# \u663E\u793A\u7ED1\u5B9A\u53C2\u6570\u7684\u5177\u4F53\u503C\uFF08\u5173\u952E\u914D\u7F6E\uFF09 logging.level.org.hibernate.type.descriptor.sql=TRACE -# 文件大小 +# \u6587\u4EF6\u5927\u5C0F spring.servlet.multipart.max-file-size=2GB spring.servlet.multipart.max-request-size=2GB mybatis.type-aliases-package=com.mir4updater.backend.entity mybatis.mapperLocations=classpath:mapper/*.xml -# redis基础配置 -# 0也是默认值,表示你要操控的 Redis 上的哪个数据库 +# redis\u57FA\u7840\u914D\u7F6E +# 0\u4E5F\u662F\u9ED8\u8BA4\u503C\uFF0C\u8868\u793A\u4F60\u8981\u64CD\u63A7\u7684 Redis \u4E0A\u7684\u54EA\u4E2A\u6570\u636E\u5E93 spring.data.redis.database=0 -# 6379也是默认值,表示 Redis 端口 +# 6379\u4E5F\u662F\u9ED8\u8BA4\u503C\uFF0C\u8868\u793A Redis \u7AEF\u53E3 spring.data.redis.port=6379 -# 这里填写你的服务器地址 +# \u8FD9\u91CC\u586B\u5199\u4F60\u7684\u670D\u52A1\u5668\u5730\u5740 spring.data.redis.host=127.0.0.1 spring.data.redis.password=fanhuitong -# 可省略 +# \u53EF\u7701\u7565 spring.data.redis.lettuce.pool.min-idle=5 spring.data.redis.lettuce.pool.max-idle=10 spring.data.redis.lettuce.pool.max-active=8