增加上传apk生成patch文件

This commit is contained in:
2025-04-14 09:26:49 +08:00
parent 02cf38acda
commit a878bf8c40
13 changed files with 497 additions and 249 deletions

8
DockerFile Normal file
View File

@@ -0,0 +1,8 @@
FROM eclipse-temurin:17-jdk-jammy
MAINTAINER TongTongStudio <tongtongstudios@gmail.com>
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"]

View File

@@ -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";
}

View File

@@ -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);
}

View File

@@ -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<String> 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);
}
}
}

View File

@@ -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<ApkInfo> apkInfoList = apkInfoService.getApkInfo(packageName);
Optional<ApkInfo> apkInfoOptional = apkInfoList.stream().max(new Comparator<ApkInfo>() {
@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<String, Object> 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<? extends ZipEntry> 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("未知错误");
}
}

View File

@@ -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<? extends ZipEntry> 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 {

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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<ApkInfo, Long> {
//}

View File

@@ -0,0 +1,8 @@
package com.mir4updater.backend.service;
import org.springframework.stereotype.Service;
@Service
public class DiffPathService {
}

View File

@@ -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<? extends ZipEntry> 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;
}
}
}

View File

@@ -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<String> 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;
}
}
}

View File

@@ -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