fix: allow store submission when online version is older than submitted
- Add compareVersionCodes() helper to compare numeric versionCodes safely - preflightStoreSubmission: block only when onlineVersionCode >= submittedCode; allow submit when online < submitted (normal new release) - refreshStoreReviewStatus: only write preExisting=true when online >= submitted - pollStoreReviewStatus: same guard for UNDER_REVIEW/REJECTED → ONLINE transitions - All per-store query methods (Huawei, Xiaomi, OPPO, VIVO, Honor): only set nonCurrentRelease=true when onlineVersionCode > submittedCode - Fix pre-existing compilation errors: replace findByAppKeyAndStoreType with findTopByAppKeyAndStoreTypeOrderByUpdatedAtDesc Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
这个提交包含在:
父节点
1a0ef7d886
当前提交
94fda7ad6e
@ -71,7 +71,7 @@ public class AppStoreService {
|
|||||||
boolean enabled) {
|
boolean enabled) {
|
||||||
boolean isCreate = configRepo.findTopByAppKeyAndStoreTypeOrderByUpdatedAtDesc(appKey, storeType).isEmpty();
|
boolean isCreate = configRepo.findTopByAppKeyAndStoreTypeOrderByUpdatedAtDesc(appKey, storeType).isEmpty();
|
||||||
AppStoreConfigEntity entity = configRepo
|
AppStoreConfigEntity entity = configRepo
|
||||||
.findByAppKeyAndStoreType(appKey, storeType)
|
.findTopByAppKeyAndStoreTypeOrderByUpdatedAtDesc(appKey, storeType)
|
||||||
.orElseGet(AppStoreConfigEntity::new);
|
.orElseGet(AppStoreConfigEntity::new);
|
||||||
|
|
||||||
if (entity.getId() == null) {
|
if (entity.getId() == null) {
|
||||||
|
|||||||
@ -200,7 +200,7 @@ public class StoreSubmissionService {
|
|||||||
// no stale webhooks or poll cycles can trigger auto-publish from the old review.
|
// no stale webhooks or poll cycles can trigger auto-publish from the old review.
|
||||||
if ("WITHDRAWN".equals(activeState)) {
|
if ("WITHDRAWN".equals(activeState)) {
|
||||||
AppStoreConfigEntity withdrawCfg = configRepo
|
AppStoreConfigEntity withdrawCfg = configRepo
|
||||||
.findByAppKeyAndStoreType(v.getAppKey(), AppStoreConfigEntity.StoreType.valueOf(storeType))
|
.findTopByAppKeyAndStoreTypeOrderByUpdatedAtDesc(v.getAppKey(), AppStoreConfigEntity.StoreType.valueOf(storeType))
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (withdrawCfg != null && withdrawCfg.isEnabled()) {
|
if (withdrawCfg != null && withdrawCfg.isEnabled()) {
|
||||||
try {
|
try {
|
||||||
@ -220,7 +220,7 @@ public class StoreSubmissionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AppStoreConfigEntity cfg = configRepo
|
AppStoreConfigEntity cfg = configRepo
|
||||||
.findByAppKeyAndStoreType(v.getAppKey(), AppStoreConfigEntity.StoreType.valueOf(storeType))
|
.findTopByAppKeyAndStoreTypeOrderByUpdatedAtDesc(v.getAppKey(), AppStoreConfigEntity.StoreType.valueOf(storeType))
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (cfg == null || !cfg.isEnabled()) {
|
if (cfg == null || !cfg.isEnabled()) {
|
||||||
skippedCount.incrementAndGet();
|
skippedCount.incrementAndGet();
|
||||||
@ -432,8 +432,16 @@ public class StoreSubmissionService {
|
|||||||
state.setCanSubmit(false);
|
state.setCanSubmit(false);
|
||||||
state.setBlockReason("查询失败,不能确认状态");
|
state.setBlockReason("查询失败,不能确认状态");
|
||||||
} else if (!state.getOnlineVersionCode().isBlank() && !submittedCode.equals(state.getOnlineVersionCode())) {
|
} else if (!state.getOnlineVersionCode().isBlank() && !submittedCode.equals(state.getOnlineVersionCode())) {
|
||||||
|
int cmp = compareVersionCodes(state.getOnlineVersionCode(), submittedCode);
|
||||||
|
if (cmp > 0) {
|
||||||
state.setNonCurrentRelease(true);
|
state.setNonCurrentRelease(true);
|
||||||
|
state.setCanSubmit(false);
|
||||||
|
state.setBlockReason("应用商店已有更高版本上线,禁止提交更低版本");
|
||||||
|
} else {
|
||||||
|
// onlineVersionCode < submittedCode: normal new release, allow submit
|
||||||
|
state.setNonCurrentRelease(false);
|
||||||
state.setCanSubmit(true);
|
state.setCanSubmit(true);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
state.setCanSubmit(true);
|
state.setCanSubmit(true);
|
||||||
}
|
}
|
||||||
@ -504,7 +512,7 @@ public class StoreSubmissionService {
|
|||||||
reviewState,
|
reviewState,
|
||||||
onShelfName, onShelfCode,
|
onShelfName, onShelfCode,
|
||||||
versionName, versionCode,
|
versionName, versionCode,
|
||||||
isLive, !isLive && !onShelfCode.isBlank(),
|
isLive, !isLive && !onShelfCode.isBlank() && compareVersionCodes(onShelfCode, submittedCode) > 0,
|
||||||
true, "", sanitizeJson(resp.getBody()));
|
true, "", sanitizeJson(resp.getBody()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -523,7 +531,7 @@ public class StoreSubmissionService {
|
|||||||
// Only treat as live for the CURRENT submission when onlineVersionCode matches submittedCode.
|
// Only treat as live for the CURRENT submission when onlineVersionCode matches submittedCode.
|
||||||
boolean currentSubmissionLive = submittedCode.equals(onlineVersionCode) && !submittedCode.isBlank();
|
boolean currentSubmissionLive = submittedCode.equals(onlineVersionCode) && !submittedCode.isBlank();
|
||||||
boolean hasOnlineVersion = updateVersion || !onlineVersionCode.isBlank();
|
boolean hasOnlineVersion = updateVersion || !onlineVersionCode.isBlank();
|
||||||
boolean nonCurrentRelease = hasOnlineVersion && !currentSubmissionLive;
|
boolean nonCurrentRelease = hasOnlineVersion && !currentSubmissionLive && compareVersionCodes(onlineVersionCode, submittedCode) > 0;
|
||||||
StoreRemoteState.ReviewState reviewState;
|
StoreRemoteState.ReviewState reviewState;
|
||||||
if (appStatus == 5 && currentSubmissionLive) {
|
if (appStatus == 5 && currentSubmissionLive) {
|
||||||
reviewState = StoreRemoteState.ReviewState.REJECTED;
|
reviewState = StoreRemoteState.ReviewState.REJECTED;
|
||||||
@ -567,13 +575,15 @@ public class StoreSubmissionService {
|
|||||||
}
|
}
|
||||||
log.info("OPPO remote state: versionId={} auditInt={} onlineCode={} submittedCode={} reviewState={}",
|
log.info("OPPO remote state: versionId={} auditInt={} onlineCode={} submittedCode={} reviewState={}",
|
||||||
v.getId(), auditInt, onlineVersionCode, submittedCode, reviewState);
|
v.getId(), auditInt, onlineVersionCode, submittedCode, reviewState);
|
||||||
|
boolean currentSubmissionLive = isLive && submittedCode.equals(onlineVersionCode);
|
||||||
|
boolean nonCurrentRelease = isLive && !submittedCode.equals(onlineVersionCode) && compareVersionCodes(onlineVersionCode, submittedCode) > 0;
|
||||||
return StoreRemoteState.ok(
|
return StoreRemoteState.ok(
|
||||||
AppStoreConfigEntity.StoreType.OPPO,
|
AppStoreConfigEntity.StoreType.OPPO,
|
||||||
reviewState,
|
reviewState,
|
||||||
onlineVersionName, onlineVersionCode,
|
onlineVersionName, onlineVersionCode,
|
||||||
"", "",
|
"", "",
|
||||||
isLive && submittedCode.equals(onlineVersionCode),
|
currentSubmissionLive,
|
||||||
isLive && !submittedCode.equals(onlineVersionCode),
|
nonCurrentRelease,
|
||||||
true, "", sanitizeJson(appData));
|
true, "", sanitizeJson(appData));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -611,7 +621,7 @@ public class StoreSubmissionService {
|
|||||||
reviewState = StoreRemoteState.ReviewState.UNKNOWN;
|
reviewState = StoreRemoteState.ReviewState.UNKNOWN;
|
||||||
}
|
}
|
||||||
boolean currentSubmissionLive = isLive && submittedCode.equals(onlineVersionCode);
|
boolean currentSubmissionLive = isLive && submittedCode.equals(onlineVersionCode);
|
||||||
boolean nonCurrentRelease = isLive && !submittedCode.equals(onlineVersionCode);
|
boolean nonCurrentRelease = isLive && !submittedCode.equals(onlineVersionCode) && compareVersionCodes(onlineVersionCode, submittedCode) > 0;
|
||||||
log.info("VIVO remote state queried: versionId={} submittedCode={} onlineVersionCode={} onlineVersionName={} status={} reviewState={} currentSubmissionLive={} nonCurrentRelease={}",
|
log.info("VIVO remote state queried: versionId={} submittedCode={} onlineVersionCode={} onlineVersionName={} status={} reviewState={} currentSubmissionLive={} nonCurrentRelease={}",
|
||||||
v.getId(), submittedCode, onlineVersionCode, onlineVersionName, status, reviewState, currentSubmissionLive, nonCurrentRelease);
|
v.getId(), submittedCode, onlineVersionCode, onlineVersionName, status, reviewState, currentSubmissionLive, nonCurrentRelease);
|
||||||
StoreRemoteState state = StoreRemoteState.ok(
|
StoreRemoteState state = StoreRemoteState.ok(
|
||||||
@ -654,13 +664,15 @@ public class StoreSubmissionService {
|
|||||||
} else {
|
} else {
|
||||||
reviewState = StoreRemoteState.ReviewState.UNKNOWN;
|
reviewState = StoreRemoteState.ReviewState.UNKNOWN;
|
||||||
}
|
}
|
||||||
|
boolean currentSubmissionLive = isLive && submittedCode.equals(onlineVersionCode);
|
||||||
|
boolean nonCurrentRelease = isLive && !submittedCode.equals(onlineVersionCode) && compareVersionCodes(onlineVersionCode, submittedCode) > 0;
|
||||||
return StoreRemoteState.ok(
|
return StoreRemoteState.ok(
|
||||||
AppStoreConfigEntity.StoreType.HONOR,
|
AppStoreConfigEntity.StoreType.HONOR,
|
||||||
reviewState,
|
reviewState,
|
||||||
onlineVersionName, onlineVersionCode,
|
onlineVersionName, onlineVersionCode,
|
||||||
"", "",
|
"", "",
|
||||||
isLive && submittedCode.equals(onlineVersionCode),
|
currentSubmissionLive,
|
||||||
isLive && !submittedCode.equals(onlineVersionCode),
|
nonCurrentRelease,
|
||||||
true, "", sanitizeJson(body));
|
true, "", sanitizeJson(body));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -710,8 +722,14 @@ public class StoreSubmissionService {
|
|||||||
if (mappedState != AppVersionEntity.StoreReviewState.UNDER_REVIEW) {
|
if (mappedState != AppVersionEntity.StoreReviewState.UNDER_REVIEW) {
|
||||||
log.info("Store review poll: {}/{} status changed to {}", v.getId(), storeType, mappedState);
|
log.info("Store review poll: {}/{} status changed to {}", v.getId(), storeType, mappedState);
|
||||||
if (mappedState == AppVersionEntity.StoreReviewState.APPROVED) {
|
if (mappedState == AppVersionEntity.StoreReviewState.APPROVED) {
|
||||||
|
int cmp = compareVersionCodes(polled.getOnlineVersionCode(), String.valueOf(v.getVersionCode()));
|
||||||
|
if (polled.isCurrentSubmissionLive() || cmp >= 0) {
|
||||||
storeService.updateStoreReviewLive(v.getId(), storeType, !polled.isCurrentSubmissionLive(),
|
storeService.updateStoreReviewLive(v.getId(), storeType, !polled.isCurrentSubmissionLive(),
|
||||||
buildLiveReason(polled), buildExtra(polled));
|
buildLiveReason(polled), buildExtra(polled));
|
||||||
|
} else {
|
||||||
|
log.debug("Store review poll: {}/{} online {} < submitted {} — keeping current state",
|
||||||
|
v.getId(), storeType, polled.getOnlineVersionCode(), v.getVersionCode());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
storeService.updateStoreReview(v.getId(), storeType, mappedState, "厂商审核状态轮询检测");
|
storeService.updateStoreReview(v.getId(), storeType, mappedState, "厂商审核状态轮询检测");
|
||||||
}
|
}
|
||||||
@ -720,11 +738,17 @@ public class StoreSubmissionService {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (polled.getReviewState() == StoreRemoteState.ReviewState.ONLINE) {
|
if (polled.getReviewState() == StoreRemoteState.ReviewState.ONLINE) {
|
||||||
|
int cmp = compareVersionCodes(polled.getOnlineVersionCode(), String.valueOf(v.getVersionCode()));
|
||||||
|
if (cmp >= 0) {
|
||||||
log.info("Store review poll: {}/{} was REJECTED but store has live version currentSubmissionLive={} nonCurrentRelease={} liveVersionName={} liveVersionCode={}",
|
log.info("Store review poll: {}/{} was REJECTED but store has live version currentSubmissionLive={} nonCurrentRelease={} liveVersionName={} liveVersionCode={}",
|
||||||
v.getId(), storeType, polled.isCurrentSubmissionLive(), polled.isNonCurrentRelease(),
|
v.getId(), storeType, polled.isCurrentSubmissionLive(), polled.isNonCurrentRelease(),
|
||||||
polled.getOnlineVersionName(), polled.getOnlineVersionCode());
|
polled.getOnlineVersionName(), polled.getOnlineVersionCode());
|
||||||
storeService.updateStoreReviewLive(v.getId(), storeType, !polled.isCurrentSubmissionLive(),
|
storeService.updateStoreReviewLive(v.getId(), storeType, !polled.isCurrentSubmissionLive(),
|
||||||
buildLiveReason(polled), buildExtra(polled));
|
buildLiveReason(polled), buildExtra(polled));
|
||||||
|
} else {
|
||||||
|
log.debug("Store review poll: {}/{} was REJECTED but online {} < submitted {} — not marking pre-existing",
|
||||||
|
v.getId(), storeType, polled.getOnlineVersionCode(), v.getVersionCode());
|
||||||
|
}
|
||||||
} else if ("MI".equals(storeType)
|
} else if ("MI".equals(storeType)
|
||||||
&& polled.getReviewState() == StoreRemoteState.ReviewState.UNDER_REVIEW_XIAOMI) {
|
&& polled.getReviewState() == StoreRemoteState.ReviewState.UNDER_REVIEW_XIAOMI) {
|
||||||
log.info("Store review poll: {}/{} was REJECTED but Xiaomi has no current-version reject signal — restoring UNDER_REVIEW",
|
log.info("Store review poll: {}/{} was REJECTED but Xiaomi has no current-version reject signal — restoring UNDER_REVIEW",
|
||||||
@ -825,11 +849,19 @@ public class StoreSubmissionService {
|
|||||||
buildLiveReason(polled), buildExtra(polled));
|
buildLiveReason(polled), buildExtra(polled));
|
||||||
} else if (!isApproved) {
|
} else if (!isApproved) {
|
||||||
// Store has a different live version (preExisting) and we didn't have APPROVED yet.
|
// Store has a different live version (preExisting) and we didn't have APPROVED yet.
|
||||||
// Mark as pre-existing live so UI shows which version is actually on the store.
|
// Only mark as pre-existing when the online version is >= submitted version.
|
||||||
|
// If online < submitted, this is a normal new-release scenario — do not write
|
||||||
|
// APPROVED+nonCurrentRelease which would block the submission UI.
|
||||||
|
int cmp = compareVersionCodes(polled.getOnlineVersionCode(), String.valueOf(v.getVersionCode()));
|
||||||
|
if (cmp >= 0) {
|
||||||
log.info("Manual refresh: {}/{} pre-existing live detected currentSubmissionLive={} liveVersionCode={}",
|
log.info("Manual refresh: {}/{} pre-existing live detected currentSubmissionLive={} liveVersionCode={}",
|
||||||
v.getId(), storeType, false, polled.getOnlineVersionCode());
|
v.getId(), storeType, false, polled.getOnlineVersionCode());
|
||||||
storeService.updateStoreReviewLive(v.getId(), storeType, true,
|
storeService.updateStoreReviewLive(v.getId(), storeType, true,
|
||||||
buildLiveReason(polled), buildExtra(polled));
|
buildLiveReason(polled), buildExtra(polled));
|
||||||
|
} else {
|
||||||
|
log.info("Manual refresh: {}/{} online version {} < submitted {} — normal new release, skipping pre-existing mark",
|
||||||
|
v.getId(), storeType, polled.getOnlineVersionCode(), v.getVersionCode());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Already APPROVED (from webhook): version approved but pending distribution.
|
// Already APPROVED (from webhook): version approved but pending distribution.
|
||||||
// Do NOT overwrite with nonCurrentRelease=true — that would show a misleading
|
// Do NOT overwrite with nonCurrentRelease=true — that would show a misleading
|
||||||
@ -1552,7 +1584,7 @@ public class StoreSubmissionService {
|
|||||||
for (String storeType : targets) {
|
for (String storeType : targets) {
|
||||||
try {
|
try {
|
||||||
AppStoreConfigEntity cfg = configRepo
|
AppStoreConfigEntity cfg = configRepo
|
||||||
.findByAppKeyAndStoreType(v.getAppKey(), AppStoreConfigEntity.StoreType.valueOf(storeType))
|
.findTopByAppKeyAndStoreTypeOrderByUpdatedAtDesc(v.getAppKey(), AppStoreConfigEntity.StoreType.valueOf(storeType))
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (cfg != null && cfg.isEnabled()) {
|
if (cfg != null && cfg.isEnabled()) {
|
||||||
try {
|
try {
|
||||||
@ -1755,7 +1787,7 @@ public class StoreSubmissionService {
|
|||||||
// Try to call HUAWEI API to update release plan if token available
|
// Try to call HUAWEI API to update release plan if token available
|
||||||
try {
|
try {
|
||||||
AppStoreConfigEntity cfg = configRepo
|
AppStoreConfigEntity cfg = configRepo
|
||||||
.findByAppKeyAndStoreType(v.getAppKey(), AppStoreConfigEntity.StoreType.HUAWEI)
|
.findTopByAppKeyAndStoreTypeOrderByUpdatedAtDesc(v.getAppKey(), AppStoreConfigEntity.StoreType.HUAWEI)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (cfg != null && cfg.isEnabled()) {
|
if (cfg != null && cfg.isEnabled()) {
|
||||||
Map<String, String> creds = parseConfig(cfg.getConfigJson());
|
Map<String, String> creds = parseConfig(cfg.getConfigJson());
|
||||||
@ -2639,4 +2671,18 @@ public class StoreSubmissionService {
|
|||||||
String full = root.getClass().getSimpleName() + ": " + message;
|
String full = root.getClass().getSimpleName() + ": " + message;
|
||||||
return full.length() > 900 ? full.substring(0, 900) + "..." : full;
|
return full.length() > 900 ? full.substring(0, 900) + "..." : full;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two version-code strings numerically.
|
||||||
|
* @return positive if online > submitted, negative if online < submitted, 0 if equal or unparseable.
|
||||||
|
*/
|
||||||
|
private static int compareVersionCodes(String onlineCode, String submittedCode) {
|
||||||
|
try {
|
||||||
|
int online = Integer.parseInt(onlineCode.trim());
|
||||||
|
int submitted = Integer.parseInt(submittedCode.trim());
|
||||||
|
return Integer.compare(online, submitted);
|
||||||
|
} catch (NumberFormatException | NullPointerException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户