增加上传apk生成patch文件
This commit is contained in:
8
DockerFile
Normal file
8
DockerFile
Normal 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"]
|
||||
10
src/main/java/com/mir4updater/backend/config/FilePath.java
Normal file
10
src/main/java/com/mir4updater/backend/config/FilePath.java
Normal 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";
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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("未知错误");
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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> {
|
||||
//}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.mir4updater.backend.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DiffPathService {
|
||||
|
||||
}
|
||||
60
src/main/java/com/mir4updater/backend/utils/ApkUtils.java
Normal file
60
src/main/java/com/mir4updater/backend/utils/ApkUtils.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
82
src/main/java/com/mir4updater/backend/utils/PatchUtils.java
Normal file
82
src/main/java/com/mir4updater/backend/utils/PatchUtils.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user