diff --git a/src/main/java/com/youlai/boot/app/packages/component/ApkMetaParser.java b/src/main/java/com/youlai/boot/app/packages/component/ApkMetaParser.java
index 40c88f50..9c0207fd 100644
--- a/src/main/java/com/youlai/boot/app/packages/component/ApkMetaParser.java
+++ b/src/main/java/com/youlai/boot/app/packages/component/ApkMetaParser.java
@@ -1,8 +1,6 @@
package com.youlai.boot.app.packages.component;
-import cn.hutool.json.JSONArray;
-import cn.hutool.json.JSONObject;
-import cn.hutool.json.JSONUtil;
+import com.android.aapt.Resources;
import com.android.tools.build.bundletool.commands.DumpCommand;
import com.android.tools.build.bundletool.model.AndroidManifest;
import com.android.tools.build.bundletool.model.AppBundle;
@@ -11,6 +9,8 @@ import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttribute
import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElement;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.youlai.boot.app.packages.model.dto.ApkmJsonInfo;
+import com.youlai.boot.app.packages.model.dto.SplitApks;
+import com.youlai.boot.app.packages.model.dto.XapkJsonInfo;
import net.dongliu.apk.parser.ApkFile;
import net.dongliu.apk.parser.bean.ApkMeta;
import net.dongliu.apk.parser.bean.Icon;
@@ -24,6 +24,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
@@ -44,7 +45,8 @@ import java.util.zip.ZipInputStream;
*/
public class ApkMetaParser {
private static final String ANDROID_NS = "http://schemas.android.com/apk/res/android";
- private Logger logger = LoggerFactory.getLogger(ApkMetaParser.class);
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+ private static final Logger LOGGER = LoggerFactory.getLogger(ApkMetaParser.class);
/**
* 统一解析入口:根据文件扩展名分发到对应解析策略。
@@ -189,6 +191,32 @@ public class ApkMetaParser {
return apkMetaResult;
}
+ /**
+ * 在资源表中按资源 ID(packageId/typeId/entryId)定位到目标条目。
+ */
+ private static Resources.Entry findResourceEntry(
+ Resources.ResourceTable table, int resId) {
+ int packageId = (resId >>> 24) & 0xFF;
+ int typeId = (resId >>> 16) & 0xFF;
+ int entryId = resId & 0xFFFF;
+ for (Resources.Package pkg : table.getPackageList()) {
+ if (pkg.getPackageId().getId() != packageId) {
+ continue;
+ }
+ for (Resources.Type type : pkg.getTypeList()) {
+ if (type.getTypeId().getId() != typeId) {
+ continue;
+ }
+ for (Resources.Entry entry : type.getEntryList()) {
+ if (entry.getEntryId().getId() == entryId) {
+ return entry;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
/**
* 从 AAB base 模块的资源表中,按资源 ID 解析字符串资源(如 @string/app_name)。
*
@@ -198,34 +226,20 @@ public class ApkMetaParser {
*/
private static String resolveStringResource(BundleModule baseModule, int resId) {
try {
- Optional tableOpt =
+ Optional tableOpt =
baseModule.getResourceTable();
if (!tableOpt.isPresent()) {
return null;
}
- com.android.aapt.Resources.ResourceTable table = tableOpt.get();
- int packageId = (resId >>> 24) & 0xFF;
- int typeId = (resId >>> 16) & 0xFF;
- int entryId = resId & 0xFFFF;
- for (com.android.aapt.Resources.Package pkg : table.getPackageList()) {
- if (pkg.getPackageId().getId() != packageId) {
- continue;
- }
- for (com.android.aapt.Resources.Type type : pkg.getTypeList()) {
- if (type.getTypeId().getId() != typeId) {
- continue;
- }
- for (com.android.aapt.Resources.Entry entry : type.getEntryList()) {
- if (entry.getEntryId().getId() != entryId) {
- continue;
- }
- for (com.android.aapt.Resources.ConfigValue cv : entry.getConfigValueList()) {
- com.android.aapt.Resources.Item item = cv.getValue().getItem();
- if (item != null && item.getValueCase() == com.android.aapt.Resources.Item.ValueCase.STR) {
- return item.getStr().getValue();
- }
- }
- }
+ Resources.ResourceTable table = tableOpt.get();
+ Resources.Entry entry = findResourceEntry(table, resId);
+ if (entry == null) {
+ return null;
+ }
+ for (Resources.ConfigValue cv : entry.getConfigValueList()) {
+ Resources.Item item = cv.getValue().getItem();
+ if (item != null && item.getValueCase() == Resources.Item.ValueCase.STR) {
+ return item.getStr().getValue();
}
}
} catch (Exception ignored) {
@@ -243,61 +257,47 @@ public class ApkMetaParser {
*/
private static String resolveFileResource(BundleModule baseModule, int resId) {
try {
- Optional tableOpt =
+ Optional tableOpt =
baseModule.getResourceTable();
if (!tableOpt.isPresent()) {
return null;
}
- com.android.aapt.Resources.ResourceTable table = tableOpt.get();
- int packageId = (resId >>> 24) & 0xFF;
- int typeId = (resId >>> 16) & 0xFF;
- int entryId = resId & 0xFFFF;
- for (com.android.aapt.Resources.Package pkg : table.getPackageList()) {
- if (pkg.getPackageId().getId() != packageId) {
+ Resources.ResourceTable table = tableOpt.get();
+ Resources.Entry entry = findResourceEntry(table, resId);
+ if (entry == null) {
+ return null;
+ }
+ String bestPath = null;
+ int bestDensity = -1;
+ boolean hasDensity = false;
+ for (Resources.ConfigValue cv : entry.getConfigValueList()) {
+ Resources.Item item = cv.getValue().getItem();
+ if (item == null
+ || item.getValueCase() != Resources.Item.ValueCase.FILE) {
continue;
}
- for (com.android.aapt.Resources.Type type : pkg.getTypeList()) {
- if (type.getTypeId().getId() != typeId) {
- continue;
- }
- for (com.android.aapt.Resources.Entry entry : type.getEntryList()) {
- if (entry.getEntryId().getId() != entryId) {
- continue;
- }
- String bestPath = null;
- int bestDensity = -1;
- boolean hasDensity = false;
- for (com.android.aapt.Resources.ConfigValue cv : entry.getConfigValueList()) {
- com.android.aapt.Resources.Item item = cv.getValue().getItem();
- if (item == null
- || item.getValueCase() != com.android.aapt.Resources.Item.ValueCase.FILE) {
- continue;
- }
- String path = item.getFile().getPath();
- if (path == null || path.isBlank()) {
- continue;
- }
- int density = cv.getConfig().getDensity();
- if (density > 0) {
- hasDensity = true;
- if (density > bestDensity) {
- bestDensity = density;
- bestPath = path;
- }
- } else if (bestPath == null) {
- // 无 density 信息的配置值,作为兜底
- bestPath = path;
- }
- }
- if (hasDensity && bestPath != null) {
- return bestPath;
- }
- // 存在带 density 的配置时走上面的分支;否则返回无 density 的第一个兜底路径
- if (bestPath != null) {
- return bestPath;
- }
- }
+ String path = item.getFile().getPath();
+ if (path == null || path.isBlank()) {
+ continue;
}
+ int density = cv.getConfig().getDensity();
+ if (density > 0) {
+ hasDensity = true;
+ if (density > bestDensity) {
+ bestDensity = density;
+ bestPath = path;
+ }
+ } else if (bestPath == null) {
+ // 无 density 信息的配置值,作为兜底
+ bestPath = path;
+ }
+ }
+ if (hasDensity && bestPath != null) {
+ return bestPath;
+ }
+ // 存在带 density 的配置时走上面的分支;否则返回无 density 的第一个兜底路径
+ if (bestPath != null) {
+ return bestPath;
}
} catch (Exception ignored) {
// 资源解析失败不影响其它字段
@@ -355,113 +355,70 @@ public class ApkMetaParser {
//以上3个应该都存在,否则解析失败
- byte[] infoData;
- byte[] iconBytes;
+ ApkmContent content = readApkmContent(apkmFile);
+ byte[] infoData = content.infoJson;
+ List apks = content.apks;
+ List splitApkList = apks.stream().map(apk -> apk.name).collect(Collectors.toList());
+ apkMetaResult.setSplitApkList(splitApkList);
- if (infoExists) {
- infoData = ZipUtil.unpackEntry(apkmFile, "info.json");
- ObjectMapper objectMapper = new ObjectMapper();
- ApkmJsonInfo info = objectMapper.readValue(infoData, ApkmJsonInfo.class);
- apkMetaResult.setAppLabel(info.getApp_name());
- apkMetaResult.setPackageName(info.getPname());
- apkMetaResult.setVersionName(info.getRelease_version());
- apkMetaResult.setVersionCode(info.getVersioncode());
- apkMetaResult.setMinSdk(info.getMin_api());
- apkMetaResult.setAbiList(info.getArches());
+ ApkmJsonInfo info = OBJECT_MAPPER.readValue(infoData, ApkmJsonInfo.class);
+ apkMetaResult.setAppLabel(info.getApp_name());
+ apkMetaResult.setPackageName(info.getPname());
+ apkMetaResult.setVersionName(info.getRelease_version());
+ apkMetaResult.setVersionCode(info.getVersioncode());
+ apkMetaResult.setMinSdk(info.getMin_api());
+ apkMetaResult.setAbiList(info.getArches());
- //info.json 无法获取到 targetSdk,compileSdk,permissions,iconPath
- //先加载icon.png兜底,后续再加载base.apk,有些base.apk无法获取到图标
- iconBytes = ZipUtil.unpackEntry(apkmFile, "icon.png");
- apkMetaResult.setIconBytes(iconBytes);
- apkMetaResult.setIconPath("icon.png");
+ //info.json 无法获取到 targetSdk,compileSdk,permissions,iconPath
+ //先加载icon.png兜底,后续再加载base.apk,有些base.apk无法获取到图标
+ byte[] iconBytes = ZipUtil.unpackEntry(apkmFile, "icon.png");
+ apkMetaResult.setIconPath("icon.png");
+ apkMetaResult.setIconBytes(iconBytes);
- byte[] baseApkbytes = ZipUtil.unpackEntry(apkmFile, "base.apk");
- ApkMetaResult result = tryParseApkBytes(baseApkbytes);
- if (result != null) {
- if (!Objects.equals(apkMetaResult.getAppLabel(),result.getAppLabel())){
- apkMetaResult.setAppLabel(result.getAppLabel());
- }
- if (!Objects.equals(apkMetaResult.getPackageName(),result.getPackageName())){
- apkMetaResult.setPackageName(result.getPackageName());
- }
- if (!Objects.equals(apkMetaResult.getVersionName(),result.getVersionName())){
- apkMetaResult.setVersionName(result.getVersionName());
- }
- if (!Objects.equals(apkMetaResult.getVersionCode(), result.getVersionCode())){
- apkMetaResult.setVersionCode(result.getVersionCode());
- }
+ byte[] baseApkbytes = ZipUtil.unpackEntry(apkmFile, "base.apk");
+ ApkMetaResult result = tryParseApkBytes(baseApkbytes);
- //这三个属性无法从info.json中获取,只能从base.apk中获取
- apkMetaResult.setTargetSdk(result.getTargetSdk());
- apkMetaResult.setCompileSdk(result.getCompileSdk());
- apkMetaResult.setPermissions(result.getPermissions());
-
- if (result.getAbiList()!= null && !result.getAbiList().isEmpty()){
- apkMetaResult.setAbiList(result.getAbiList());
- }
- if(result.getSplitApkList()!= null && !result.getSplitApkList().isEmpty()){
- apkMetaResult.setSplitApkList(result.getSplitApkList());
- }
-
- if (StringUtils.isNotBlank(result.getIconPath())) {
- apkMetaResult.setIconPath(result.getIconPath());
- }
- if (result.getIconBytes() != null) {
- apkMetaResult.setIconBytes(result.getIconBytes());
- }
+ if (result != null) {
+ if (!Objects.equals(apkMetaResult.getAppLabel(), result.getAppLabel())) {
+ apkMetaResult.setAppLabel(result.getAppLabel());
+ }
+ if (!Objects.equals(apkMetaResult.getPackageName(), result.getPackageName())) {
+ apkMetaResult.setPackageName(result.getPackageName());
+ }
+ if (!Objects.equals(apkMetaResult.getVersionName(), result.getVersionName())) {
+ apkMetaResult.setVersionName(result.getVersionName());
+ }
+ if (!Objects.equals(apkMetaResult.getVersionCode(), result.getVersionCode())) {
+ apkMetaResult.setVersionCode(result.getVersionCode());
}
+ //这三个属性无法从info.json中获取,只能从base.apk中获取
+ apkMetaResult.setTargetSdk(result.getTargetSdk());
+ apkMetaResult.setCompileSdk(result.getCompileSdk());
+ apkMetaResult.setPermissions(result.getPermissions());
- return apkMetaResult;
- } else {
-
- ApkmContent content = readApkmContent(apkmFile);
- List apks = content.apks;
- byte[] infoJson = content.infoJson;
-
- if (apks.isEmpty()) {
- throw new IOException("容器内未找到任何 .apk 文件: " + apkmFile.getName());
+ if (isNotEmpty(result.getAbiList())) {
+ apkMetaResult.setAbiList(result.getAbiList());
+ }
+ if (isNotEmpty(result.getSplitApkList())) {
+ apkMetaResult.setSplitApkList(result.getSplitApkList());
}
-
- byte[] baseApkbytes = ZipUtil.unpackEntry(apkmFile, "base.apk");
-
- // 1) 按经验法则优先定位主 APK
- ContainerEntry mainApk = locateApkmMainApk(apks);
-
- // 2) 若经验法则未命中,兜底:逐个尝试解析,第一个拿到 packageName 的即为主包
- ApkMetaResult result = tryParseApkBytes(baseApkbytes);
- if (result == null) {
- for (ContainerEntry entry : apks) {
- ApkMetaResult r = tryParseApkBytes(entry.data);
- if (r != null && r.getPackageName() != null && !r.getPackageName().isBlank()) {
- result = r;
- mainApk = entry;
- break;
- }
- }
+ if (StringUtils.isNotBlank(result.getIconPath())) {
+ apkMetaResult.setIconPath(result.getIconPath());
}
- if (result == null) {
- throw new IOException("容器内所有 apk 均无法解析出有效包名: " + apkmFile.getName());
+ if (result.getIconBytes() != null) {
+ apkMetaResult.setIconBytes(result.getIconBytes());
}
-
- // 3) 记录 split apk 列表
- for (ContainerEntry entry : apks) {
- result.getSplitApkList().add(entry.name);
- }
-
- if (infoJson != null) {
- supplementFromJson(result, infoJson);
- }
-
- return result;
}
+
+ return apkMetaResult;
}
/**
* 解包 ZIP 容器,收集内部所有 .apk 及描述文件(info.json)。
* APKM固定为info.json
- *
+ * 只获取splitApkList和info.json信息
*
*/
private static ApkmContent readApkmContent(File apkmFile) throws IOException {
@@ -475,7 +432,7 @@ public class ApkMetaParser {
continue;
}
String entryName = entry.getName();
- if (entryName.toLowerCase(Locale.ROOT).endsWith(".apk")) {
+ if (entryName.toLowerCase(Locale.ROOT).endsWith(".apk") && !entryName.equalsIgnoreCase("base.apk")) {
try (InputStream in = zf.getInputStream(entry)) {
apks.add(new ContainerEntry(entryName, in.readAllBytes()));
}
@@ -505,44 +462,63 @@ public class ApkMetaParser {
* 可能额外包含大型 obb 扩展文件(非 apk,直接忽略)。
*/
public static ApkMetaResult parseXapk(File xapkFile) throws IOException {
+ ApkMetaResult apkMetaResult = new ApkMetaResult();
+
+ boolean manifestExists = ZipUtil.containsEntry(xapkFile, "manifest.json");
+
+ if (!manifestExists) {
+ throw new IOException("容器内未找到 manifest.json 文件: " + xapkFile.getName());
+ }
+
XapkContent content = readXapkContent(xapkFile);
List apks = content.apks;
byte[] manifestJson = content.manifestJson;
- if (apks.isEmpty()) {
- throw new IOException("容器内未找到任何 .apk 文件: " + xapkFile.getName());
+ XapkJsonInfo xapkJsonInfo = OBJECT_MAPPER.readValue(manifestJson, XapkJsonInfo.class);
+
+ apkMetaResult.setAppLabel(xapkJsonInfo.getName());
+ apkMetaResult.setPackageName(xapkJsonInfo.getPackage_name());
+ apkMetaResult.setVersionName(xapkJsonInfo.getVersion_name());
+ apkMetaResult.setVersionCode(xapkJsonInfo.getVersion_code());
+ apkMetaResult.setMinSdk(xapkJsonInfo.getMin_sdk_version());
+ apkMetaResult.setTargetSdk(xapkJsonInfo.getTarget_sdk_version());
+ apkMetaResult.setPermissions(xapkJsonInfo.getPermissions());
+
+ byte[] iconBytes = ZipUtil.unpackEntry(xapkFile, xapkJsonInfo.getIcon());
+ apkMetaResult.setIconPath(xapkJsonInfo.getIcon());
+ apkMetaResult.setIconBytes(iconBytes);
+
+ List splitConfigs = xapkJsonInfo.getSplit_configs();
+ List splitApksList = xapkJsonInfo.getSplit_apks();
+ Set splitConfigSet = new HashSet<>(splitConfigs);
+ List fileList = xapkJsonInfo.getSplit_apks().stream()
+ .filter(apk -> splitConfigSet.contains(apk.getId()))
+ .map(SplitApks::getFile)
+ .collect(Collectors.toList());
+ apkMetaResult.setSplitApkList(fileList);
+
+ SplitApks baseApk = splitApksList.stream().filter(splitApks -> splitApks.getId().equals("base")).findFirst().orElse(null);
+ if (baseApk == null) {
+ throw new IOException("容器内未找到 base apk 文件: " + xapkFile.getName());
}
- // 1) XAPK 主包优先按 manifest.json 的 split_apks(id == "base") 定位,失败再走经验法则
- ContainerEntry mainApk = locateXapkMainApk(apks, manifestJson);
+ ContainerEntry baseApkEntry = apks.stream().filter(containerEntry -> containerEntry.name.equals(baseApk.getFile())).findFirst().orElse(null);
+ if (baseApkEntry == null) {
+ throw new IOException("容器内未找到 base apk 文件: " + xapkFile.getName());
+ }
// 2) 若经验法则未命中,兜底:逐个尝试解析,第一个拿到 packageName 的即为主包
- ApkMetaResult result = mainApk != null ? tryParseApkBytes(mainApk.data) : null;
+ ApkMetaResult result = tryParseApkBytes(baseApkEntry.data);
if (result == null) {
- for (ContainerEntry entry : apks) {
- ApkMetaResult r = tryParseApkBytes(entry.data);
- if (r != null && r.getPackageName() != null && !r.getPackageName().isBlank()) {
- result = r;
- mainApk = entry;
- break;
- }
- }
- }
- if (result == null) {
- throw new IOException("容器内所有 apk 均无法解析出有效包名: " + xapkFile.getName());
+ throw new IOException("容器内未找到 base apk 文件: " + xapkFile.getName());
}
- // 3) 记录 split apk 列表
- for (ContainerEntry entry : apks) {
- result.getSplitApkList().add(entry.name);
- }
+ // 主 APK 解析出的字段优先(覆盖容器 JSON 中的值)
+ overrideIfDifferent(apkMetaResult, result);
+ apkMetaResult.setCompileSdk(result.getCompileSdk());
+ apkMetaResult.setAbiList(result.getAbiList());
- // 4) 补充 JSON 描述信息(XAPK 通常只有 manifest.json)
- if (manifestJson != null) {
- supplementFromJson(result, manifestJson);
- }
-
- return result;
+ return apkMetaResult;
}
private static XapkContent readXapkContent(File xapkFile) throws IOException {
@@ -570,64 +546,6 @@ public class ApkMetaParser {
return new XapkContent(apks, manifestJson);
}
- /**
- * 经验法则定位主 APK:
- *
- * - APKM(APKMirror):info.json 描述的 base.apk
- *
- */
- private static ContainerEntry locateApkmMainApk(List apks) {
- // 优先 base-master.apk / base.apk
- for (ContainerEntry e : apks) {
- if (e.name.toLowerCase(Locale.ROOT).equalsIgnoreCase("base.apk")) {
- return e;
- }
- }
- // 再找不含 split / config / density 等关键字的主包
- for (ContainerEntry e : apks) {
- String lower = e.name.toLowerCase(Locale.ROOT);
- if (!lower.contains("split") && !lower.contains("config") && !lower.contains("density")) {
- return e;
- }
- }
- return null;
- }
-
- /**
- * XAPK 专属定位:解析 manifest.json 的 {@code split_apks} 数组,返回
- * {@code id == "base"} 项对应的 apk 条目(即主包)。
- */
- private static ContainerEntry locateXapkMainApk(List apks, byte[] manifestJson) {
- if (manifestJson == null) {
- return null;
- }
- String baseFile = null;
- try {
- JSONArray splits = JSONUtil.parseObj(new String(manifestJson, StandardCharsets.UTF_8))
- .getJSONArray("split_apks");
- if (splits != null) {
- for (int i = 0; i < splits.size(); i++) {
- JSONObject split = splits.getJSONObject(i);
- if ("base".equals(split.getStr("id"))) {
- baseFile = split.getStr("file");
- break;
- }
- }
- }
- } catch (Exception ignored) {
- // JSON 解析失败则回退到经验法则
- }
- if (baseFile == null) {
- return null;
- }
- for (ContainerEntry e : apks) {
- if (e.name.equalsIgnoreCase(baseFile)) {
- return e;
- }
- }
- return null;
- }
-
private record XapkContent(List apks, byte[] manifestJson) {
}
@@ -870,6 +788,34 @@ public class ApkMetaParser {
}
}
+ /**
+ * 将主 APK 解析结果覆盖到容器解析结果上,仅当两者不一致时才覆盖。
+ */
+ private static void overrideIfDifferent(ApkMetaResult target, ApkMetaResult source) {
+ if (!Objects.equals(target.getAppLabel(), source.getAppLabel())) {
+ target.setAppLabel(source.getAppLabel());
+ }
+ if (!Objects.equals(target.getPackageName(), source.getPackageName())) {
+ target.setPackageName(source.getPackageName());
+ }
+ if (!Objects.equals(target.getVersionName(), source.getVersionName())) {
+ target.setVersionName(source.getVersionName());
+ }
+ if (!Objects.equals(target.getVersionCode(), source.getVersionCode())) {
+ target.setVersionCode(source.getVersionCode());
+ }
+ if (!Objects.equals(target.getMinSdk(), source.getMinSdk())) {
+ target.setMinSdk(source.getMinSdk());
+ }
+ if (!Objects.equals(target.getTargetSdk(), source.getTargetSdk())) {
+ target.setTargetSdk(source.getTargetSdk());
+ }
+ }
+
+ private static boolean isNotEmpty(List> list) {
+ return list != null && !list.isEmpty();
+ }
+
private static void fillIcon(ApkMetaResult result, ApkFile apkFileParser) {
try {
List icons = apkFileParser.getIconFiles();
diff --git a/src/main/java/com/youlai/boot/app/packages/model/dto/SplitApks.java b/src/main/java/com/youlai/boot/app/packages/model/dto/SplitApks.java
new file mode 100644
index 00000000..abeaa516
--- /dev/null
+++ b/src/main/java/com/youlai/boot/app/packages/model/dto/SplitApks.java
@@ -0,0 +1,25 @@
+package com.youlai.boot.app.packages.model.dto;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class SplitApks implements Serializable {
+
+ private static final long serialVersionUID = 4207274362982520838L;
+
+ private String file;
+ private String id;
+
+ @Override
+ public String toString() {
+ try {
+ return new ObjectMapper().writeValueAsString(this);
+ } catch (JsonProcessingException e) {
+ return super.toString();
+ }
+ }
+}
diff --git a/src/main/java/com/youlai/boot/app/packages/model/dto/XapkJsonInfo.java b/src/main/java/com/youlai/boot/app/packages/model/dto/XapkJsonInfo.java
new file mode 100644
index 00000000..26c480d7
--- /dev/null
+++ b/src/main/java/com/youlai/boot/app/packages/model/dto/XapkJsonInfo.java
@@ -0,0 +1,37 @@
+package com.youlai.boot.app.packages.model.dto;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+
+public class XapkJsonInfo implements Serializable {
+
+ private static final long serialVersionUID = -5080591968988248631L;
+
+ private int xapk_version;
+ private String package_name;
+ private String name;
+ private long version_code;
+ private String version_name;
+ private int min_sdk_version;
+ private int target_sdk_version;
+ private List permissions;
+ private List split_configs;
+ private long total_size;
+ private String icon;
+ private List split_apks;
+
+ @Override
+ public String toString() {
+ try {
+ return new ObjectMapper().writeValueAsString(this);
+ } catch (JsonProcessingException e) {
+ return super.toString();
+ }
+ }
+}
diff --git a/src/main/java/com/youlai/boot/app/packages/model/vo/AppPackageVO.java b/src/main/java/com/youlai/boot/app/packages/model/vo/AppPackageVO.java
index cb5f9356..723d6fe5 100644
--- a/src/main/java/com/youlai/boot/app/packages/model/vo/AppPackageVO.java
+++ b/src/main/java/com/youlai/boot/app/packages/model/vo/AppPackageVO.java
@@ -44,7 +44,7 @@ public class AppPackageVO {
private String versionName;
@Schema(description = "版本号")
- private Integer versionCode;
+ private Long versionCode;
@Schema(description = "最低SDK版本")
private Integer minSdk;