diff --git a/tenant-service/src/main/java/com/xuqm/tenant/controller/InternalSdkController.java b/tenant-service/src/main/java/com/xuqm/tenant/controller/InternalSdkController.java index 76f80d3..8a7014d 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/controller/InternalSdkController.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/controller/InternalSdkController.java @@ -57,6 +57,7 @@ public class InternalSdkController { } AppEntity app = provisioningService.resolveApp(appKey); return ResponseEntity.ok(ApiResponse.success(Map.of( + "name", app.getName() == null ? "" : app.getName(), "androidPackageName", app.getPackageName() == null ? "" : app.getPackageName(), "iosBundleId", app.getIosBundleId() == null ? "" : app.getIosBundleId(), "harmonyBundleName", app.getHarmonyBundleName() == null ? "" : app.getHarmonyBundleName() diff --git a/update-service/src/main/java/com/xuqm/update/repository/AppVersionRepository.java b/update-service/src/main/java/com/xuqm/update/repository/AppVersionRepository.java index 9d83f45..38f2f88 100644 --- a/update-service/src/main/java/com/xuqm/update/repository/AppVersionRepository.java +++ b/update-service/src/main/java/com/xuqm/update/repository/AppVersionRepository.java @@ -35,4 +35,7 @@ public interface AppVersionRepository extends JpaRepository findAllWithUnderReviewStores(); + + List findByAppKeyAndPlatformAndVersionCodeAndIdNot( + String appKey, AppVersionEntity.Platform platform, int versionCode, String id); } diff --git a/update-service/src/main/java/com/xuqm/update/service/AppStoreService.java b/update-service/src/main/java/com/xuqm/update/service/AppStoreService.java index 0cf1727..90c5a80 100644 --- a/update-service/src/main/java/com/xuqm/update/service/AppStoreService.java +++ b/update-service/src/main/java/com/xuqm/update/service/AppStoreService.java @@ -1,6 +1,7 @@ package com.xuqm.update.service; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.xuqm.update.entity.AppStoreConfigEntity; import com.xuqm.update.entity.AppVersionEntity; @@ -11,6 +12,7 @@ import com.xuqm.update.repository.RnBundleRepository; import com.xuqm.update.service.UpdateOperationLogService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -30,6 +32,13 @@ public class AppStoreService { private static final ObjectMapper mapper = new ObjectMapper(); private static final Set ACTIVE_REVIEW_STATES = Set.of("PENDING", "SUBMITTING", "UNDER_REVIEW"); private final HttpClient http = HttpClient.newHttpClient(); + private final ConcurrentMap appNameCache = new ConcurrentHashMap<>(); + + @Value("${sdk.tenant-service-url:http://xuqm-tenant-service:9001}") + private String tenantServiceUrl; + + @Value("${sdk.internal-token:xuqm-internal-token}") + private String internalToken; private final AppStoreConfigRepository configRepo; private final AppVersionRepository versionRepo; @@ -437,6 +446,28 @@ public class AppStoreService { } } + private String resolveAppName(String appKey) { + return appNameCache.computeIfAbsent(appKey, key -> { + try { + String url = tenantServiceUrl.replaceAll("/+$", "") + "/api/internal/sdk/apps/" + key + "/platform-info"; + HttpRequest req = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("X-Internal-Token", internalToken) + .GET() + .build(); + HttpResponse resp = http.send(req, HttpResponse.BodyHandlers.ofString()); + if (resp.statusCode() == 200) { + JsonNode body = mapper.readTree(resp.body()); + String name = body.path("data").path("name").asText(""); + if (!name.isBlank()) return name; + } + } catch (Exception e) { + log.warn("Failed to resolve app name for {}: {}", key, e.getMessage()); + } + return key; + }); + } + private String buildWebhookBody(String notifyType, AppVersionEntity v, String storeType, AppVersionEntity.StoreReviewState state, String reason) throws Exception { String stateLabel = switch (state) { @@ -449,8 +480,9 @@ public class AppStoreService { }; String storeLabel = storeDisplayName(storeType); String reasonSuffix = (reason != null && !reason.isBlank()) ? "\n原因:" + reason : ""; + String appName = resolveAppName(v.getAppKey()); String text = String.format("【应用审核通知】\n应用:%s\n版本:%s(%d)\n渠道:%s\n状态:%s%s", - v.getAppKey(), v.getVersionName(), v.getVersionCode(), + appName, v.getVersionName(), v.getVersionCode(), storeLabel, stateLabel, reasonSuffix); return switch (notifyType.toUpperCase()) { diff --git a/update-service/src/main/java/com/xuqm/update/service/StoreSubmissionService.java b/update-service/src/main/java/com/xuqm/update/service/StoreSubmissionService.java index 95d83ba..f548e07 100644 --- a/update-service/src/main/java/com/xuqm/update/service/StoreSubmissionService.java +++ b/update-service/src/main/java/com/xuqm/update/service/StoreSubmissionService.java @@ -134,6 +134,8 @@ public class StoreSubmissionService { return; } + withdrawSupersededApprovedStores(v); + AtomicInteger successCount = new AtomicInteger(); AtomicInteger rejectedCount = new AtomicInteger(); AtomicInteger skippedCount = new AtomicInteger(); @@ -1142,6 +1144,45 @@ public class StoreSubmissionService { } } + /** + * When a new version entity is submitted with the same versionCode as an existing one, + * mark APPROVED stores on the old entity as WITHDRAWN (DB-only, no store API call). + * This clears the superseded record without triggering notifications. + */ + private void withdrawSupersededApprovedStores(AppVersionEntity newVersion) { + List superseded = versionRepo.findByAppKeyAndPlatformAndVersionCodeAndIdNot( + newVersion.getAppKey(), newVersion.getPlatform(), newVersion.getVersionCode(), newVersion.getId()); + for (AppVersionEntity old : superseded) { + if (old.getStoreReviewStatus() == null || !old.getStoreReviewStatus().contains("APPROVED")) continue; + try { + @SuppressWarnings("unchecked") + Map> reviewMap = + mapper.readValue(old.getStoreReviewStatus(), new TypeReference<>() {}); + boolean changed = false; + for (Map.Entry> entry : reviewMap.entrySet()) { + Object state = entry.getValue().get("state"); + if ("APPROVED".equals(state != null ? state.toString() : "")) { + Map updated = new LinkedHashMap<>(entry.getValue()); + updated.put("state", "WITHDRAWN"); + updated.put("stage", "WITHDRAWN"); + updated.put("reason", "已被新版本包替代"); + updated.put("updatedAt", LocalDateTime.now().toString()); + entry.setValue(updated); + changed = true; + log.info("Superseding APPROVED store {} on old version {} (versionCode={})", + entry.getKey(), old.getId(), old.getVersionCode()); + } + } + if (changed) { + old.setStoreReviewStatus(mapper.writeValueAsString(reviewMap)); + versionRepo.save(old); + } + } catch (Exception e) { + log.warn("Failed to supersede approved stores for old version {}: {}", old.getId(), e.getMessage()); + } + } + } + private List extractActiveReviewTargets(AppVersionEntity v) { if (v.getStoreReviewStatus() == null || v.getStoreReviewStatus().isBlank()) return List.of(); try {