fix(update): fix OPPO token expiry, sign empty params, and MI already-live detection
- OPPO: re-obtain access token after APK upload (prevents expiry during long uploads) - OPPO: exclude empty-string params from api_sign computation per OPPO spec - OPPO: include errno in error message for easier debugging - MI: detect already-live version via packageInfo.versionCode before upload; throw VersionAlreadyLiveException → executor marks state APPROVED instead of REJECTED Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
450a44de68
当前提交
501d7e09ab
@ -988,6 +988,19 @@ public class StoreSubmissionService {
|
||||
"reviewState", AppVersionEntity.StoreReviewState.UNDER_REVIEW.name()
|
||||
), null);
|
||||
log.info("Submitted version {} to {}", versionId, plan.storeType);
|
||||
} catch (VersionAlreadyLiveException e) {
|
||||
// Version is already the live version on this store — mark as APPROVED, not REJECTED
|
||||
log.info("Store {} version {} already live, updating to APPROVED: {}", plan.storeType, versionId, e.getMessage());
|
||||
try {
|
||||
storeService.updateStoreReview(versionId, plan.storeType,
|
||||
AppVersionEntity.StoreReviewState.APPROVED);
|
||||
recordStoreEvent(v, versionId, batchId, plan.storeType, "STORE_SUBMIT_ALREADY_LIVE", Map.of(
|
||||
"durationMs", System.currentTimeMillis() - plan.storeStartedAt,
|
||||
"reason", e.getMessage()
|
||||
), null);
|
||||
} catch (Exception ex) {
|
||||
log.warn("Failed to persist already-live state for {}/{}: {}", v.getAppKey(), plan.storeType, ex.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
rejectedCount.incrementAndGet();
|
||||
String message = describeException(e);
|
||||
@ -1443,7 +1456,16 @@ public class StoreSubmissionService {
|
||||
String packageName = requirePackageName(v);
|
||||
JsonNode appInfo = miGetAppInfo(account, packageName, publicKey, privateKey);
|
||||
String appName = appInfo.path("packageInfo").path("appName").asText(packageName);
|
||||
// If Xiaomi's current live versionCode already matches this submission, skip upload
|
||||
String onlineCode = appInfo.path("packageInfo").path("versionCode").asText("");
|
||||
String submittedCode = String.valueOf(v.getVersionCode());
|
||||
if (!submittedCode.isBlank() && submittedCode.equals(onlineCode)) {
|
||||
throw new VersionAlreadyLiveException("MI",
|
||||
"MI 版本 " + v.getVersionName() + "(" + submittedCode + ") 已是小米在线版本,无需重复提交");
|
||||
}
|
||||
miUploadApk(file, account, appName, packageName, v.getChangeLog(), publicKey, privateKey);
|
||||
} catch (VersionAlreadyLiveException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("MI store submission failed: " + e.getMessage(), e);
|
||||
}
|
||||
@ -1464,6 +1486,8 @@ public class StoreSubmissionService {
|
||||
}
|
||||
Map<String, String> uploadUrl = oppoGetUploadUrl(token, clientId, clientSecret);
|
||||
JsonNode apkResult = oppoUploadApk(uploadUrl, token, file, clientSecret);
|
||||
// Re-obtain token: the upload can take minutes and the OPPO token may expire
|
||||
token = oppoGetToken(clientId, clientSecret);
|
||||
oppoSubmit(v, file, token, appInfo, apkResult, clientSecret);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("OPPO store submission failed: " + e.getMessage(), e);
|
||||
@ -2066,9 +2090,9 @@ public class StoreSubmissionService {
|
||||
|
||||
private void oppoCheckSuccess(JsonNode root, String action) {
|
||||
int code = root.path("errno").asInt(-1);
|
||||
String message = root.path("data").path("message").asText("");
|
||||
String message = root.path("data").path("message").asText(root.path("errMsg").asText("未知"));
|
||||
if (code != 0) {
|
||||
throw new IllegalStateException(action + " failed: " + message);
|
||||
throw new IllegalStateException(action + " failed (errno=" + code + "): " + message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2078,7 +2102,8 @@ public class StoreSubmissionService {
|
||||
List<String> parts = new ArrayList<>();
|
||||
for (String key : keys) {
|
||||
String value = paramsMap.get(key);
|
||||
if (value == null) continue;
|
||||
// OPPO sign algorithm: skip null and empty-string params
|
||||
if (value == null || value.isEmpty()) continue;
|
||||
parts.add(key + "=" + value);
|
||||
}
|
||||
return hmacSha256(String.join("&", parts), secret);
|
||||
@ -2370,6 +2395,16 @@ public class StoreSubmissionService {
|
||||
return mapper.readValue(json, new TypeReference<Map<String, String>>() {});
|
||||
}
|
||||
|
||||
/** Thrown when the version being submitted is already the live version on the target store. */
|
||||
private static final class VersionAlreadyLiveException extends RuntimeException {
|
||||
private final String storeType;
|
||||
VersionAlreadyLiveException(String storeType, String msg) {
|
||||
super(msg);
|
||||
this.storeType = storeType;
|
||||
}
|
||||
String getStoreType() { return storeType; }
|
||||
}
|
||||
|
||||
private static final class SubmissionPlan {
|
||||
private final String storeType;
|
||||
private final Map<String, String> creds;
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户