diff --git a/src/main/java/com/lion/lionwebsite/Service/SubscriptionRefreshService.java b/src/main/java/com/lion/lionwebsite/Service/SubscriptionRefreshService.java index eaa810d..75f472e 100644 --- a/src/main/java/com/lion/lionwebsite/Service/SubscriptionRefreshService.java +++ b/src/main/java/com/lion/lionwebsite/Service/SubscriptionRefreshService.java @@ -41,6 +41,9 @@ public class SubscriptionRefreshService { @Value("${subscription.upstream.high-multiplier-threshold:2.0}") double highMultiplierThreshold; + @Value("${subscription.cache-root:sub/accounts}") + String cacheRoot; + public boolean refreshAll() { boolean success = true; for (SubscriptionAccount account : subMapper.selectAllSubscriptionAccounts()) { @@ -60,7 +63,7 @@ public class SubscriptionRefreshService { try { String v2 = processV2(firstLine(download(v2Url(account))), account.isFilterHighMultiplier(), highMultiplierThreshold); List clash = processClash(download(clashUrl(account)), account.isFilterHighMultiplier(), highMultiplierThreshold); - Path dir = Paths.get("sub", "accounts", String.valueOf(accountId)); + Path dir = Paths.get(cacheRoot, String.valueOf(accountId)); Files.createDirectories(dir); atomicWrite(dir.resolve("v2ray.txt"), v2.getBytes(StandardCharsets.UTF_8)); atomicWrite(dir.resolve("clash.yaml"), String.join("\n", clash).concat("\n").getBytes(StandardCharsets.UTF_8)); @@ -205,7 +208,7 @@ public class SubscriptionRefreshService { } public Path cachedPath(Integer accountId, String client) { - return Paths.get("sub", "accounts", String.valueOf(accountId), client.equals("v2") ? "v2ray.txt" : "clash.yaml"); + return Paths.get(cacheRoot, String.valueOf(accountId), client.equals("v2") ? "v2ray.txt" : "clash.yaml"); } public boolean hasCompleteCache(Integer accountId) { diff --git a/src/main/java/com/lion/lionwebsite/Service/SubscriptionStandbySnapshotService.java b/src/main/java/com/lion/lionwebsite/Service/SubscriptionStandbySnapshotService.java index ebb4c7c..50d5abf 100644 --- a/src/main/java/com/lion/lionwebsite/Service/SubscriptionStandbySnapshotService.java +++ b/src/main/java/com/lion/lionwebsite/Service/SubscriptionStandbySnapshotService.java @@ -27,7 +27,6 @@ import java.util.concurrent.locks.Lock; @RequiredArgsConstructor public class SubscriptionStandbySnapshotService { private final SubMapper subMapper; - private final SubscriptionRefreshService refreshService; private final SubscriptionStateCoordinator stateCoordinator; private final ObjectMapper objectMapper = CustomUtil.objectMapper; @@ -37,6 +36,9 @@ public class SubscriptionStandbySnapshotService { @Value("${subscription.standby.sync-enabled:false}") boolean syncEnabled; + @Value("${subscription.cache-root:sub/accounts}") + String cacheRoot; + @PostConstruct void validateConfiguration() { if (syncEnabled && (syncSecret == null || syncSecret.isBlank())) @@ -49,10 +51,10 @@ public class SubscriptionStandbySnapshotService { try { Map accountMap = new HashMap<>(); for (SubscriptionAccount account : subMapper.selectAllSubscriptionAccounts()) { - if (!account.isEnabled() || !refreshService.hasCompleteCache(account.getId())) + Path v2Path = cachedPath(account.getId(), "v2"); + Path clashPath = cachedPath(account.getId(), "cat"); + if (!account.isEnabled() || !Files.isRegularFile(v2Path) || !Files.isRegularFile(clashPath)) continue; - Path v2Path = refreshService.cachedPath(account.getId(), "v2"); - Path clashPath = refreshService.cachedPath(account.getId(), "cat"); byte[] v2 = Files.readAllBytes(v2Path); byte[] clash = Files.readAllBytes(clashPath); SubscriptionAccountSnapshot snapshot = new SubscriptionAccountSnapshot(); @@ -129,4 +131,8 @@ public class SubscriptionStandbySnapshotService { } private static String hex(byte[] input) { return HexFormat.of().formatHex(input); } + + private Path cachedPath(Integer accountId, String client) { + return Path.of(cacheRoot, String.valueOf(accountId), "v2".equals(client) ? "v2ray.txt" : "clash.yaml"); + } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index e9703b9..53bb996 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -37,6 +37,7 @@ local: dou-nai-v2ray: "https://aaaa.gay/link/{key}?client=v2" subscription: + cache-root: sub/accounts upstream: # Use environment variables or an external config file in production. v2-url-template: "https://aaaa.gay/link/{key}?client=v2" diff --git a/src/test/java/com/lion/lionwebsite/Service/SubscriptionStandbySnapshotServiceTest.java b/src/test/java/com/lion/lionwebsite/Service/SubscriptionStandbySnapshotServiceTest.java index de252f3..0b90d65 100644 --- a/src/test/java/com/lion/lionwebsite/Service/SubscriptionStandbySnapshotServiceTest.java +++ b/src/test/java/com/lion/lionwebsite/Service/SubscriptionStandbySnapshotServiceTest.java @@ -13,35 +13,39 @@ import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Base64; import java.util.zip.GZIPInputStream; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; class SubscriptionStandbySnapshotServiceTest { @Test void buildsDeterministicSnapshotWithoutPlainPublicKeys(@TempDir Path directory) throws Exception { - SubMapper mapper = mock(SubMapper.class); - SubscriptionRefreshService refreshService = mock(SubscriptionRefreshService.class); - SubscriptionStateCoordinator coordinator = new SubscriptionStateCoordinator(); SubscriptionAccount account = new SubscriptionAccount(1, "account", "upstream-secret", true, true, null, null, null, null, 2, null, null); - when(mapper.selectAllSubscriptionAccounts()).thenReturn(new ArrayList<>(java.util.List.of(account))); - when(mapper.selectAllSubBind()).thenReturn(new ArrayList<>(java.util.List.of( + ArrayList accounts = new ArrayList<>(java.util.List.of(account)); + ArrayList bindings = new ArrayList<>(java.util.List.of( new SubBind("public-key-a", "user-a", 1, "account", true, true), - new SubBind("public-key-b", "user-b", 1, "account", true, true)))); - Path v2 = directory.resolve("v2ray.txt"); - Path clash = directory.resolve("clash.yaml"); + new SubBind("public-key-b", "user-b", 1, "account", true, true))); + SubMapper mapper = (SubMapper) Proxy.newProxyInstance(SubMapper.class.getClassLoader(), + new Class[]{SubMapper.class}, (proxy, method, args) -> switch (method.getName()) { + case "selectAllSubscriptionAccounts" -> accounts; + case "selectAllSubBind" -> bindings; + default -> throw new UnsupportedOperationException(method.getName()); + }); + SubscriptionStateCoordinator coordinator = new SubscriptionStateCoordinator(); + Path accountDirectory = directory.resolve("1"); + Files.createDirectories(accountDirectory); + Path v2 = accountDirectory.resolve("v2ray.txt"); + Path clash = accountDirectory.resolve("clash.yaml"); Files.writeString(v2, "v2-content", StandardCharsets.UTF_8); Files.writeString(clash, "clash-content", StandardCharsets.UTF_8); - when(refreshService.hasCompleteCache(1)).thenReturn(true); - when(refreshService.cachedPath(1, "v2")).thenReturn(v2); - when(refreshService.cachedPath(1, "cat")).thenReturn(clash); - SubscriptionStandbySnapshotService service = new SubscriptionStandbySnapshotService(mapper, refreshService, coordinator); + SubscriptionStandbySnapshotService service = new SubscriptionStandbySnapshotService(mapper, coordinator); service.syncSecret = "test-sync-secret"; + service.cacheRoot = directory.toString(); SubscriptionSnapshotMessage first = service.build(); SubscriptionSnapshotMessage second = service.build();