feat(storage): 新增应用更新目录并切换存储至 S3,新增应用管理日志模块

This commit is contained in:
tongtong committed 2026-11-04 11:36:24 +08:00
1 parent 1829238de6
commit 4b746255da
58 files changed
+4906 -10

No files matched your search

@@ -0,0 +1,102 @@
package com.youlai.boot.app.update.component;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* APK 增量差分工具测试。
* <p>
* 覆盖两点:当前平台能定位到内置的 {@code hdiffz}(且与 classpath 资源逐字节一致,
* 防止资源被过滤/损坏);对高度相似的文件生成的补丁明显小于新包。
*/
@DisplayName("APK 增量差分工具测试")
class ApkDeltaUpdaterTest {
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
private static final int SIZE = 4 * 1024 * 1024;
private static final int CHANGED_OFFSET = 1024 * 1024;
private static final int CHANGED_LENGTH = 256 * 1024;
@Test
@DisplayName("当前平台应定位到可用的内置 hdiffz,且与资源逐字节一致")
void shouldPrepareBinaryForCurrentPlatform() throws IOException {
ApkDeltaUpdater.Platform platform = ApkDeltaUpdater.currentPlatform();
assertNotNull(platform);
Path hdiffz = ApkDeltaUpdater.binaryDir().resolve(platform.hdiffzName());
assertTrue(Files.isRegularFile(hdiffz), "hdiffz 不存在: " + hdiffz);
assertTrue(platform.isWindows() || Files.isExecutable(hdiffz), "hdiffz 不可执行: " + hdiffz);
assertEquals(sha256OfResource("/hdiffpatch/" + platform.folder() + "/" + platform.hdiffzName()),
sha256OfFile(hdiffz), "释放到工作目录的二进制与 classpath 资源不一致");
}
@Test
@DisplayName("相似文件应生成明显小于新包的补丁")
void shouldCreateCompactPatchForSimilarFiles(@TempDir Path dir)
throws IOException, InterruptedException {
byte[] oldBytes = randomBytes(SIZE);
byte[] newBytes = Arrays.copyOf(oldBytes, SIZE);
System.arraycopy(randomBytes(CHANGED_LENGTH), 0, newBytes, CHANGED_OFFSET, CHANGED_LENGTH);
File oldFile = dir.resolve("old.bin").toFile();
File newFile = dir.resolve("new.bin").toFile();
File patchFile = dir.resolve("delta.patch").toFile();
Files.write(oldFile.toPath(), oldBytes);
Files.write(newFile.toPath(), newBytes);
ApkDeltaUpdater.DeltaPlan plan = ApkDeltaUpdater.DeltaPlan.forFiles(oldFile, newFile);
assertEquals(ApkDeltaUpdater.SizeTier.SMALL, plan.tier(), "4MB 应落在 SMALL 档");
ApkDeltaUpdater.createPatch(oldFile, newFile, patchFile, plan);
long patchSize = patchFile.length();
assertTrue(patchSize > 0, "补丁为空");
// 仅改动 6.25% 内容,补丁应远小于新包(未改动部分由 hdiffz 以 copy 指令引用)
assertTrue(patchSize < newFile.length() / 4,
"补丁过大:" + patchSize + " / " + newFile.length());
}
private static byte[] randomBytes(int length) {
byte[] bytes = new byte[length];
SECURE_RANDOM.nextBytes(bytes);
return bytes;
}
private static String sha256OfResource(String resource) throws IOException {
try (InputStream in = ApkDeltaUpdater.class.getResourceAsStream(resource)) {
assertNotNull(in, "classpath 缺少内置二进制: " + resource);
return sha256(in.readAllBytes());
}
}
private static String sha256OfFile(Path file) throws IOException {
return sha256(Files.readAllBytes(file));
}
private static String sha256(byte[] bytes) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
StringBuilder text = new StringBuilder();
for (byte b : digest.digest(bytes)) {
text.append(String.format("%02x", b));
}
return text.toString();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}